Bug 1819253 - Retry IOUtils.remove on E_ACCESSDENIED r=nika

Differential Revision: https://phabricator.services.mozilla.com/D171183
This commit is contained in:
Barret Rennie 2023-03-08 15:35:00 +00:00
parent 8b7a8c4b9d
commit adc87dc487
4 changed files with 64 additions and 9 deletions

View File

@ -576,6 +576,14 @@ dictionary RemoveOptions {
* If true, and the target is a directory, recursively remove files.
*/
boolean recursive = false;
/**
* If true, a failed delete on a readonly file will be retried by first
* removing the readonly attribute.
*
* Only has an effect on Windows.
*/
boolean retryReadonly = false;
};
/**

View File

@ -665,8 +665,9 @@ already_AddRefed<Promise> IOUtils::Remove(GlobalObject& aGlobal,
DispatchAndResolve<Ok>(
state->mEventQueue, promise,
[file = std::move(file), ignoreAbsent = aOptions.mIgnoreAbsent,
recursive = aOptions.mRecursive]() {
return RemoveSync(file, ignoreAbsent, recursive);
recursive = aOptions.mRecursive,
retryReadonly = aOptions.mRetryReadonly]() {
return RemoveSync(file, ignoreAbsent, recursive, retryReadonly);
});
});
}
@ -1613,9 +1614,13 @@ Result<Ok, IOUtils::IOError> IOUtils::CopyOrMoveSync(CopyOrMoveFn aMethod,
/* static */
Result<Ok, IOUtils::IOError> IOUtils::RemoveSync(nsIFile* aFile,
bool aIgnoreAbsent,
bool aRecursive) {
bool aRecursive,
bool aRetryReadonly) {
MOZ_ASSERT(!NS_IsMainThread());
// Prevent an unused variable warning.
(void)aRetryReadonly;
nsresult rv = aFile->Remove(aRecursive);
if (aIgnoreAbsent && IsFileNotFound(rv)) {
return Ok();
@ -1634,6 +1639,17 @@ Result<Ok, IOUtils::IOError> IOUtils::RemoveSync(nsIFile* aFile,
"Specify the `recursive: true` option to mitigate this error",
aFile->HumanReadablePath().get()));
}
#ifdef XP_WIN
if (rv == NS_ERROR_FILE_ACCESS_DENIED && aRetryReadonly) {
MOZ_TRY(SetWindowsAttributesSync(aFile, 0, FILE_ATTRIBUTE_READONLY));
return RemoveSync(aFile, aIgnoreAbsent, aRecursive,
/* aRetryReadonly = */ false);
}
#endif
return Err(err.WithMessage("Could not remove the file at %s",
aFile->HumanReadablePath().get()));
}

View File

@ -401,16 +401,20 @@ class IOUtils final {
/**
* Attempts to remove the file located at |aFile|.
*
* @param aFile The location of the file.
* @param aIgnoreAbsent If true, suppress errors due to an absent target file.
* @param aRecursive If true, attempt to recursively remove descendant
* files. This option is safe to use even if the target
* is not a directory.
* @param aFile The location of the file.
* @param aIgnoreAbsent If true, suppress errors due to an absent target
* file.
* @param aRecursive If true, attempt to recursively remove descendant
* files. This option is safe to use even if the target
* is not a directory.
* @param aRetryReadonly Retry a delete that failed with a NotAllowedError by
* first removing the readonly attribute. Only has an
* effect on Windows.
*
* @return Ok if the file was removed successfully, or an error.
*/
static Result<Ok, IOError> RemoveSync(nsIFile* aFile, bool aIgnoreAbsent,
bool aRecursive);
bool aRecursive, bool aRetryReadonly);
/**
* Attempts to create a new directory at |aFile|.

View File

@ -78,6 +78,33 @@
"IOUtils::remove can recursively remove a directory"
);
});
if (Services.appinfo.OS === "WINNT") {
add_task(async function test_remove_retry_readonly() {
const tmpDir = PathUtils.join(PathUtils.tempDir, "test_ioutils_remove_retry_readonly.tmp.d");
const path = PathUtils.join(tmpDir, "file.txt");
await createDir(tmpDir);
await createFile(path, "");
await IOUtils.setWindowsAttributes(path, { readOnly: true });
await Assert.rejects(
IOUtils.remove(path),
/NotAllowedError/,
"Cannot remove a readonly file by default"
);
Assert.ok(await fileExists(path), "File should still exist");
await IOUtils.remove(path, { retryReadonly: true });
Assert.ok(!await fileExists(path), "File should not exist");
await IOUtils.remove(tmpDir);
});
}
</script>
</head>