Bug 1745361 - Create unique directories and files with IOUtils r=Gijs,webdriver-reviewers,extension-reviewers,robwu

PathUtils::CreateUniquePath was doing main thread IO, so we're moving it to
IOUtils to use its event loop to do the IO.

Additionally, we're adding IOUtils::CreateUniqueDirectory which is the same as
::CreateUniqueFile, but for directories.

Differential Revision: https://phabricator.services.mozilla.com/D133841
This commit is contained in:
Barret Rennie 2022-01-18 05:04:52 +00:00
Родитель 9460e18a04
Коммит 32cf1f6e27
13 изменённых файлов: 217 добавлений и 57 удалений

Просмотреть файл

@ -214,6 +214,28 @@ namespace IOUtils {
*/
Promise<boolean> exists(DOMString path);
/**
* Create a file with a unique name and return its path.
*
* @param parent An absolute path to the directory where the file is to be
* created.
* @param prefix A prefix for the filename.
*
* @return A promise that resolves to a unique filename.
*/
Promise<DOMString> createUniqueFile(DOMString parent, DOMString prefix, optional unsigned long permissions = 0644);
/**
* Create a directory with a unique name and return its path.
*
* @param parent An absolute path to the directory where the file is to be
* created.
* @param prefix A prefix for the directory name.
*
* @return A promise that resolves to a unique directory name.
*/
Promise<DOMString> createUniqueDirectory(DOMString parent, DOMString prefix, optional unsigned long permissions = 0755);
#if defined(XP_WIN)
/**
* Return the Windows-specific file attributes of the file at the given path.

Просмотреть файл

@ -50,14 +50,6 @@ namespace PathUtils {
[Throws]
DOMString joinRelative(DOMString base, DOMString relativePath);
/**
* Creates a unique path from the provided path.
*
* @param path An absolute path.
*/
[Throws]
DOMString createUniquePath(DOMString path);
/**
* Creates an adjusted path using a path whose length is already close
* to MAX_PATH. For windows only.

Просмотреть файл

@ -712,6 +712,49 @@ already_AddRefed<Promise> IOUtils::Exists(GlobalObject& aGlobal,
});
}
/* static */
already_AddRefed<Promise> IOUtils::CreateUniqueFile(
GlobalObject& aGlobal, const nsAString& aParent, const nsAString& aPrefix,
const uint32_t aPermissions) {
return CreateUnique(aGlobal, aParent, aPrefix, nsIFile::NORMAL_FILE_TYPE,
aPermissions);
}
/* static */
already_AddRefed<Promise> IOUtils::CreateUniqueDirectory(
GlobalObject& aGlobal, const nsAString& aParent, const nsAString& aPrefix,
const uint32_t aPermissions) {
return CreateUnique(aGlobal, aParent, aPrefix, nsIFile::DIRECTORY_TYPE,
aPermissions);
}
/* static */
already_AddRefed<Promise> IOUtils::CreateUnique(GlobalObject& aGlobal,
const nsAString& aParent,
const nsAString& aPrefix,
const uint32_t aFileType,
const uint32_t aPermissions) {
return WithPromiseAndState(aGlobal, [&](Promise* promise, auto& state) {
nsCOMPtr<nsIFile> file = new nsLocalFile();
REJECT_IF_INIT_PATH_FAILED(file, aParent, promise);
if (nsresult rv = file->Append(aPrefix); NS_FAILED(rv)) {
RejectJSPromise(
promise,
IOError(rv).WithMessage("Could not append prefix `%s' to parent `%s'",
NS_ConvertUTF16toUTF8(aPrefix).get(),
file->HumanReadablePath().get()));
return;
}
DispatchAndResolve<nsString>(
state->mEventQueue, promise,
[file = std::move(file), aPermissions, aFileType]() {
return CreateUniqueSync(file, aFileType, aPermissions);
});
});
}
#if defined(XP_WIN)
/* static */
@ -1588,6 +1631,22 @@ Result<bool, IOUtils::IOError> IOUtils::ExistsSync(nsIFile* aFile) {
return exists;
}
/* static */
Result<nsString, IOUtils::IOError> IOUtils::CreateUniqueSync(
nsIFile* aFile, const uint32_t aFileType, const uint32_t aPermissions) {
MOZ_ASSERT(!NS_IsMainThread());
if (nsresult rv = aFile->CreateUnique(aFileType, aPermissions);
NS_FAILED(rv)) {
return Err(IOError(rv).WithMessage("Could not create unique path"));
}
nsString path;
MOZ_ALWAYS_SUCCEEDS(aFile->GetPath(path));
return path;
}
#if defined(XP_WIN)
Result<uint32_t, IOUtils::IOError> IOUtils::GetWindowsAttributesSync(

Просмотреть файл

@ -124,6 +124,24 @@ class IOUtils final {
static already_AddRefed<Promise> Exists(GlobalObject& aGlobal,
const nsAString& aPath);
static already_AddRefed<Promise> CreateUniqueFile(
GlobalObject& aGlobal, const nsAString& aParent, const nsAString& aPrefix,
const uint32_t aPermissions);
static already_AddRefed<Promise> CreateUniqueDirectory(
GlobalObject& aGlobal, const nsAString& aParent, const nsAString& aPrefix,
const uint32_t aPermissions);
private:
/**
* A helper method for CreateUniqueFile and CreateUniqueDirectory.
*/
static already_AddRefed<Promise> CreateUnique(GlobalObject& aGlobal,
const nsAString& aParent,
const nsAString& aPrefix,
const uint32_t aFileType,
const uint32_t aPermissions);
public:
#if defined(XP_WIN)
static already_AddRefed<Promise> GetWindowsAttributes(GlobalObject& aGlobal,
const nsAString& aPath);
@ -395,6 +413,19 @@ class IOUtils final {
*/
static Result<bool, IOError> ExistsSync(nsIFile* aFile);
/**
* Create a file or directory with a unique path.
*
* @param aFile The location of the file or directory (including prefix)
* @param aFileType One of |nsIFile::NORMAL_FILE_TYPE| or
* |nsIFile::DIRECTORY_TYPE|.
* @param aperms The permissions to create the file or directory with.
*
* @return A unique path.
*/
static Result<nsString, IOError> CreateUniqueSync(
nsIFile* aFile, const uint32_t aFileType, const uint32_t aPermissions);
#if defined(XP_WIN)
/**
* Return the Windows-specific attributes of the file.

Просмотреть файл

@ -37,7 +37,6 @@ static constexpr auto ERROR_EMPTY_PATH =
static constexpr auto ERROR_INITIALIZE_PATH = "Could not initialize path"_ns;
static constexpr auto ERROR_GET_PARENT = "Could not get parent path"_ns;
static constexpr auto ERROR_JOIN = "Could not append to path"_ns;
static constexpr auto ERROR_CREATE_UNIQUE = "Could not create unique path"_ns;
static void ThrowError(ErrorResult& aErr, const nsresult aResult,
const nsCString& aMessage) {
@ -217,28 +216,6 @@ void PathUtils::JoinRelative(const GlobalObject&, const nsAString& aBasePath,
MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
}
void PathUtils::CreateUniquePath(const GlobalObject&, const nsAString& aPath,
nsString& aResult, ErrorResult& aErr) {
if (aPath.IsEmpty()) {
aErr.ThrowNotAllowedError(ERROR_EMPTY_PATH);
return;
}
nsCOMPtr<nsIFile> path = new nsLocalFile();
if (nsresult rv = InitFileWithPath(path, aPath); NS_FAILED(rv)) {
ThrowError(aErr, rv, ERROR_INITIALIZE_PATH);
return;
}
if (nsresult rv = path->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
NS_FAILED(rv)) {
ThrowError(aErr, rv, ERROR_CREATE_UNIQUE);
return;
}
MOZ_ALWAYS_SUCCEEDS(path->GetPath(aResult));
}
void PathUtils::ToExtendedWindowsPath(const GlobalObject&,
const nsAString& aPath, nsString& aResult,
ErrorResult& aErr) {

Просмотреть файл

@ -50,9 +50,6 @@ class PathUtils final {
const nsAString& aRelativePath, nsString& aResult,
ErrorResult& aErr);
static void CreateUniquePath(const GlobalObject&, const nsAString& aPath,
nsString& aResult, ErrorResult& aErr);
static void ToExtendedWindowsPath(const GlobalObject&, const nsAString& aPath,
nsString& aResult, ErrorResult& aErr);

Просмотреть файл

@ -5,6 +5,7 @@ support-files =
[test_ioutils.html]
[test_ioutils_copy_move.html]
[test_ioutils_create_unique.html]
[test_ioutils_dir_iteration.html]
[test_ioutils_mac_xattr.html]
skip-if = (os != "mac")

Просмотреть файл

@ -0,0 +1,89 @@
<!-- Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ -->
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>Test the IOUtils file I/O API</title>
<script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
<script src="file_ioutils_test_fixtures.js"></script>
<script>
"use strict";
const { Assert } = ChromeUtils.import("resource://testing-common/Assert.jsm");
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
function octalFormat(n) {
let s = n.toString(8);
while (s.length < 3) {
s = `0${s}`;
}
return `0o${s}`;
}
async function check(method, path, prefix, type, perms) {
const filename = PathUtils.filename(path);
ok(filename.startsWith(prefix), `IOUtils.${method} uses the prefix`);
ok(await IOUtils.exists(path), `IOUtils.${method} creates a file`);
const stat = await IOUtils.stat(path);
is(stat.type, type, `IOUtils.${method} creates a "${type}" file`);
is(
octalFormat(stat.permissions),
octalFormat(perms),
`IOUtils.${method} creates a file with the correct permissions`
);
}
add_task(async function test_createUnique() {
const tempDir = PathUtils.join(
await PathUtils.getTempDir(),
"test_createUnique.tmp.d"
);
const filesToChmod = [];
SimpleTest.registerCleanupFunction(async function test_createUnique_cleanup() {
for (const file of filesToChmod) {
if (await IOUtils.exists(file)) {
await IOUtils.setPermissions(file, 0o666);
}
}
await IOUtils.remove(tempDir, { recursive: true });
});
const isWindows = Services.appinfo.OS === "WINNT";
info("Creating a unique directory")
const dir = await IOUtils.createUniqueDirectory(tempDir, "unique-dir", 0o600);
await check("createUniqueDirectory", dir, "unique-dir", "directory", isWindows ? 0o666 : 0o600);
info("Creating a unique directory with the same prefix")
const dir2 = await IOUtils.createUniqueDirectory(tempDir, "unique-dir", 0o700);
await check("createUniqueDirectory", dir2, "unique-dir", "directory", isWindows ? 0o666 : 0o700);
ok(dir !== dir2, "IOUtils.createUniqueDirectory creates unique paths");
info("Creating a unique file");
const file = await IOUtils.createUniqueFile(tempDir, "unique-file", 0o641);
await check("createUniqueFile", file, "unique-file", "regular", isWindows ? 0o666 : 0o641);
info("Creating a unique file with the same prefix");
const file2 = await IOUtils.createUniqueFile(tempDir, "unique-file", 0o400);
filesToChmod.push(file2);
await check("createUniqueFile", file2, "unique-file", "regular", isWindows ? 0o444 : 0o400);
});
</script>
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
</body>
</html>

Просмотреть файл

@ -423,21 +423,6 @@
);
}
});
add_task(async function test_createUniquePath() {
let path = PathUtils.join(await PathUtils.getProfileDir(), ".test");
let firstPath = PathUtils.createUniquePath(path);
let secondPath = PathUtils.createUniquePath(path);
SimpleTest.registerCleanupFunction(async () => {
await IOUtils.remove(firstPath);
await IOUtils.remove(secondPath);
});
isnot(firstPath, secondPath, "Create unique paths returns different paths");
is(PathUtils.filename(firstPath), ".test", "PathUtils.createUniquePath() matches filename for first path");
is(PathUtils.filename(secondPath), "-1.test", "PathUtils.createUniquePath() has unique filename for second path");
});
</script>
<body>

Просмотреть файл

@ -198,8 +198,11 @@ function parseRanges(ranges) {
print.printToFile = async function(browser, settings) {
// Create a unique filename for the temporary PDF file
const tempDir = await PathUtils.getTempDir();
const basePath = PathUtils.join(tempDir, "marionette.pdf");
const filePath = await PathUtils.createUniquePath(basePath);
const filePath = await IOUtils.createUniqueFile(
tempDir,
"marionette.pdf",
0o600
);
let printSettings = getPrintSettings(settings, filePath);

Просмотреть файл

@ -1225,8 +1225,11 @@ ContentPrefService2.prototype = {
if (aConn) {
await aConn.close();
}
let backupFile = aPath + ".corrupt";
let uniquePath = PathUtils.createUniquePath(backupFile);
let uniquePath = await IOUtils.createUniqueFile(
PathUtils.parent(aPath),
PathUtils.filename(aPath) + ".corrupt",
0o600
);
await IOUtils.copy(aPath, uniquePath);
await IOUtils.remove(aPath);
this.log("Completed DB cleanup.");

Просмотреть файл

@ -245,8 +245,10 @@ JSONFile.prototype = {
// Move the original file to a backup location, ignoring errors.
try {
let uniquePath = await PathUtils.createUniquePath(
this.path + ".corrupt"
let uniquePath = await IOUtils.createUniqueFile(
PathUtils.parent(this.path),
PathUtils.filename(this.path) + ".corrupt",
0o600
);
await IOUtils.move(this.path, uniquePath);
this._recordTelemetry("load", cleansedBasename, "invalid_json");

Просмотреть файл

@ -4050,8 +4050,7 @@ var XPIInstall = {
// ownsTempFile so that we will cleanup later (see installAddonSet).
try {
let tmpDir = Services.dirsvc.get("TmpD", Ci.nsIFile).path;
let path = PathUtils.join(tmpDir, "tmpaddon");
let uniquePath = PathUtils.createUniquePath(path);
let uniquePath = await IOUtils.createUniqueFile(tmpDir, "tmpaddon");
await IOUtils.copy(sourceAddon._sourceBundle.path, uniquePath);
// Make sure to update file modification times so this is detected
// as a new add-on.