Bug 1812243 - Rescan usages at QuotaClient initialization after crash. r=dom-storage-reviewers,janv

Differential Revision: https://phabricator.services.mozilla.com/D162584
This commit is contained in:
Jari Jalkanen 2023-02-08 13:06:36 +00:00
Родитель 94dabce14e
Коммит 030abfe7c4
13 изменённых файлов: 627 добавлений и 102 удалений

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

@ -40,6 +40,8 @@ void HandleFailedStatus(nsresult aError, const RefPtr<Promise>& aPromise) {
case NS_ERROR_FILE_ACCESS_DENIED:
aPromise->MaybeRejectWithNotAllowedError("Permission denied");
break;
case NS_ERROR_FILE_NOT_FOUND:
[[fallthrough]];
case NS_ERROR_DOM_NOT_FOUND_ERR:
aPromise->MaybeRejectWithNotFoundError("Entry not found");
break;

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

@ -168,10 +168,10 @@ bool FileSystemAccessHandle::IsInactive() const {
RefPtr<FileSystemAccessHandle::InitPromise>
FileSystemAccessHandle::BeginInit() {
if (!mDataManager->LockExclusive(mEntryId)) {
return InitPromise::CreateAndReject(
NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR, __func__);
}
QM_TRY(MOZ_TO_RESULT(mDataManager->LockExclusive(mEntryId)),
[](const nsresult aRv) {
return InitPromise::CreateAndReject(aRv, __func__);
});
mLocked = true;

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

@ -186,10 +186,8 @@ mozilla::ipc::IPCResult FileSystemManagerParent::RecvGetWritable(
AssertIsOnIOTarget();
MOZ_ASSERT(mDataManager);
if (!mDataManager->LockShared(aRequest.entryId())) {
aResolver(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
return IPC_OK();
}
QM_TRY(MOZ_TO_RESULT(mDataManager->LockShared(aRequest.entryId())), IPC_OK(),
([aResolver](const nsresult& aRv) { aResolver(aRv); }));
auto autoUnlock =
MakeScopeExit([self = RefPtr<FileSystemManagerParent>(this), aRequest] {

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

@ -127,6 +127,9 @@ Result<quota::UsageInfo, nsresult> QuotaClient::InitOrigin(
QM_TRY_INSPECT(const ResultConnection& conn,
GetStorageConnection(origin).mapErr(toNSResult));
QM_TRY(MOZ_TO_RESULT(
data::FileSystemDatabaseManager::RescanUsages(conn, origin)));
return data::FileSystemDatabaseManager::GetUsage(conn, origin)
.mapErr(toNSResult);
}

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

@ -348,15 +348,21 @@ bool FileSystemDataManager::IsLocked(const EntryId& aEntryId) const {
return mExclusiveLocks.Contains(aEntryId);
}
bool FileSystemDataManager::LockExclusive(const EntryId& aEntryId) {
nsresult FileSystemDataManager::LockExclusive(const EntryId& aEntryId) {
if (IsLocked(aEntryId)) {
return false;
return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
}
// If the file has been removed, we should get a file not found error.
// Otherwise, if usage tracking cannot be started because file size is not
// known and attempts to read it are failing, lock is denied to freeze the
// quota usage until the (external) blocker is gone or the file is removed.
QM_TRY(MOZ_TO_RESULT(mDatabaseManager->BeginUsageTracking(aEntryId)));
LOG_VERBOSE(("ExclusiveLock"));
mExclusiveLocks.Insert(aEntryId);
return true;
return NS_OK;
}
void FileSystemDataManager::UnlockExclusive(const EntryId& aEntryId) {
@ -365,10 +371,13 @@ void FileSystemDataManager::UnlockExclusive(const EntryId& aEntryId) {
LOG_VERBOSE(("ExclusiveUnlock"));
mExclusiveLocks.Remove(aEntryId);
QM_WARNONLY_TRY(MOZ_TO_RESULT(mDatabaseManager->UpdateUsage(aEntryId)));
// On error, usage tracking remains on to prevent writes until usage is
// updated successfully.
QM_TRY(MOZ_TO_RESULT(mDatabaseManager->UpdateUsage(aEntryId)), QM_VOID);
QM_TRY(MOZ_TO_RESULT(mDatabaseManager->EndUsageTracking(aEntryId)), QM_VOID);
}
bool FileSystemDataManager::LockShared(const EntryId& aEntryId) {
nsresult FileSystemDataManager::LockShared(const EntryId& aEntryId) {
return LockExclusive(aEntryId);
}

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

@ -119,11 +119,11 @@ class FileSystemDataManager
bool IsLocked(const EntryId& aEntryId) const;
bool LockExclusive(const EntryId& aEntryId);
nsresult LockExclusive(const EntryId& aEntryId);
void UnlockExclusive(const EntryId& aEntryId);
bool LockShared(const EntryId& aEntryId);
nsresult LockShared(const EntryId& aEntryId);
void UnlockShared(const EntryId& aEntryId);

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

@ -18,6 +18,29 @@
namespace mozilla::dom::fs::data {
/* static */
nsresult FileSystemDatabaseManager::RescanUsages(
const ResultConnection& aConnection, const Origin& aOrigin) {
DatabaseVersion version = 0;
QM_TRY(MOZ_TO_RESULT(aConnection->GetSchemaVersion(&version)));
switch (version) {
case 0: {
return NS_OK;
}
case 1: {
return FileSystemDatabaseManagerVersion001::RescanTrackedUsages(
aConnection, aOrigin);
}
default:
break;
}
return NS_ERROR_NOT_IMPLEMENTED;
}
/* static */
Result<quota::UsageInfo, QMResult> FileSystemDatabaseManager::GetUsage(
const ResultConnection& aConnection, const Origin& aOrigin) {

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

@ -34,6 +34,14 @@ namespace data {
class FileSystemDatabaseManager {
public:
/**
* @brief Updates stored usage data for all tracked files.
*
* @return nsresult error code
*/
static nsresult RescanUsages(const ResultConnection& aConnection,
const Origin& aOrigin);
/**
* @brief Obtains the current total usage for origin and connection.
*
@ -141,6 +149,16 @@ class FileSystemDatabaseManager {
*/
virtual void Close() = 0;
/**
* @brief Start tracking file's usage.
*/
virtual nsresult BeginUsageTracking(const EntryId& aEntryId) = 0;
/**
* @brief Stop tracking file's usage.
*/
virtual nsresult EndUsageTracking(const EntryId& aEntryId) = 0;
virtual ~FileSystemDatabaseManager() = default;
};

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

@ -6,16 +6,22 @@
#include "FileSystemDatabaseManagerVersion001.h"
#include <stdint.h>
#include "FileSystemDataManager.h"
#include "FileSystemFileManager.h"
#include "ResultStatement.h"
#include "mozStorageHelper.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/dom/FileSystemDataManager.h"
#include "mozilla/dom/FileSystemHandle.h"
#include "mozilla/dom/FileSystemLog.h"
#include "mozilla/dom/FileSystemTypes.h"
#include "mozilla/dom/PFileSystemManager.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/QuotaObject.h"
#include "mozilla/dom/quota/ResultExtensions.h"
namespace mozilla::dom {
@ -26,6 +32,8 @@ namespace fs::data {
namespace {
auto toNSResult = [](const auto& aRv) { return ToNSResult(aRv); };
Result<bool, QMResult> ApplyEntryExistsQuery(
const FileSystemConnection& aConnection, const nsACString& aQuery,
const FileSystemChildMetadata& aHandle) {
@ -47,7 +55,7 @@ Result<bool, QMResult> ApplyEntryExistsQuery(
return stmt.YesOrNoQuery();
}
Result<bool, QMResult> IsDirectoryEmpty(FileSystemConnection& mConnection,
Result<bool, QMResult> IsDirectoryEmpty(const FileSystemConnection& mConnection,
const EntryId& aEntryId) {
const nsLiteralCString isDirEmptyQuery =
"SELECT EXISTS ("
@ -359,8 +367,6 @@ nsresult PerformRename(const FileSystemConnection& aConnection,
return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
}
auto toNSResult = [](const auto& aRv) { return ToNSResult(aRv); };
// TODO: This should fail when handle doesn't exist - the
// explicit file or directory existence queries are redundant
QM_TRY_UNWRAP(ResultStatement stmt,
@ -432,8 +438,315 @@ Result<nsTArray<EntryId>, QMResult> FindDescendants(
return descendants;
}
nsresult SetUsageTracking(const FileSystemConnection& aConnection,
const EntryId& aEntryId, bool aTracked) {
const nsLiteralCString setTrackedQuery =
"INSERT INTO Usages "
"( handle, tracked ) "
"VALUES "
"( :handle, :tracked ) "
"ON CONFLICT(handle) DO "
"UPDATE SET tracked = excluded.tracked "
";"_ns;
const nsresult onMissingFile = aTracked ? NS_ERROR_DOM_NOT_FOUND_ERR : NS_OK;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, setTrackedQuery));
QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
QM_TRY(MOZ_TO_RESULT(stmt.BindBooleanByName("tracked"_ns, aTracked)));
QM_TRY(MOZ_TO_RESULT(stmt.Execute()), onMissingFile,
([&aConnection, &aEntryId](const auto& aRv) {
// Usages constrains entryId to be present in Files
MOZ_ASSERT(NS_ERROR_STORAGE_CONSTRAINT == ToNSResult(aRv));
// The query *should* fail if and only if file does not exist
QM_TRY_UNWRAP(DebugOnly<bool> fileExists,
DoesFileExist(aConnection, aEntryId), QM_VOID);
MOZ_ASSERT(!fileExists);
}));
return NS_OK;
}
Result<nsTArray<EntryId>, QMResult> GetTrackedFiles(
const FileSystemConnection& aConnection) {
static const nsLiteralCString getTrackedFilesQuery =
"SELECT handle FROM Usages WHERE tracked = TRUE;"_ns;
nsTArray<EntryId> trackedFiles;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, getTrackedFilesQuery));
QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
while (moreResults) {
QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u));
trackedFiles.AppendElement(entryId);
QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
}
return trackedFiles;
}
/** This handles the file not found error by assigning 0 usage to the dangling
* handle and puts the handle to a non-tracked state. Otherwise, when the
* file or database cannot be reached, the file remains in the tracked state.
*/
template <class QuotaCacheUpdate>
nsresult UpdateUsageForFileEntry(const FileSystemConnection& aConnection,
const FileSystemFileManager& aFileManager,
const EntryId& aEntryId,
const nsLiteralCString& aUpdateQuery,
QuotaCacheUpdate&& aUpdateCache) {
QM_TRY_INSPECT(const auto& fileHandle, aFileManager.GetFile(aEntryId));
// A file could have changed in a way which doesn't allow to read its size.
QM_TRY_UNWRAP(
const int64_t fileSize,
QM_OR_ELSE_WARN_IF(
// Expression.
MOZ_TO_RESULT_INVOKE_MEMBER(fileHandle, GetFileSize),
// Predicate.
([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
// Fallback. If the file does no longer exist, treat it as 0-sized.
ErrToDefaultOk<int64_t>));
QM_TRY(MOZ_TO_RESULT(aUpdateCache(fileSize)));
// No transaction as one statement succeeds or fails atomically
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, aUpdateQuery));
QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, fileSize)));
QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
return NS_OK;
}
nsresult UpdateUsageUnsetTracked(const FileSystemConnection& aConnection,
const FileSystemFileManager& aFileManager,
const EntryId& aEntryId) {
static const nsLiteralCString updateUsagesUnsetTrackedQuery =
"UPDATE Usages SET usage = :usage, tracked = FALSE "
"WHERE handle = :handle;"_ns;
auto noCacheUpdateNeeded = [](auto) { return NS_OK; };
return UpdateUsageForFileEntry(aConnection, aFileManager, aEntryId,
updateUsagesUnsetTrackedQuery,
std::move(noCacheUpdateNeeded));
}
/**
* @brief Get the sum of usages for all file descendants of a directory entry.
* We obtain the value with one query, which is presumably better than having a
* separate query for each individual descendant.
* TODO: Check if this is true
*
* Please see GetFileUsage documentation for why we use the latest recorded
* value from the database instead of the file size property from the disk.
*/
Result<Usage, QMResult> GetUsagesOfDescendants(
const FileSystemConnection& aConnection, const EntryId& aEntryId) {
const nsLiteralCString descendantUsagesQuery =
"WITH RECURSIVE traceChildren(handle, parent) AS ( "
"SELECT handle, parent "
"FROM Entries "
"WHERE handle=:handle "
"UNION "
"SELECT Entries.handle, Entries.parent FROM traceChildren, Entries "
"WHERE traceChildren.handle=Entries.parent ) "
"SELECT sum(Usages.usage) "
"FROM traceChildren INNER JOIN Usages "
"USING(handle) "
";"_ns;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, descendantUsagesQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
if (!moreResults) {
return 0;
}
QM_TRY_RETURN(stmt.GetUsageByColumn(/* Column */ 0u));
}
/**
* @brief Get recorded usage or zero if nothing was ever written to the file.
* Removing files is only allowed when there is no lock on the file, and their
* usage is either correctly recorded in the database during unlock, or nothing,
* or they remain in tracked state and the quota manager assumes their usage to
* be equal to the latest recorded value. In all cases, the latest recorded
* value (or nothing) is the correct amount of quota to be released.
*/
Result<Usage, QMResult> GetKnownUsage(const FileSystemConnection& aConnection,
const EntryId& aEntryId) {
const nsLiteralCString trackedUsageQuery =
"SELECT usage FROM Usages WHERE handle = :handle ;"_ns;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, trackedUsageQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
if (!moreResults) {
return 0;
}
QM_TRY_RETURN(stmt.GetUsageByColumn(/* Column */ 0u));
}
/**
* @brief Get the recorded usage only if the file is in tracked state.
* During origin initialization, if the usage on disk is unreadable, the latest
* recorded usage is reported to the quota manager for the tracked files.
* To allow writing, we attempt to update the real usage with one database and
* one file size query.
*/
Result<Maybe<Usage>, QMResult> GetMaybeTrackedUsage(
const FileSystemConnection& aConnection, const EntryId& aEntryId) {
const nsLiteralCString trackedUsageQuery =
"SELECT usage FROM Usages WHERE tracked = TRUE AND handle = :handle "
");"_ns;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, trackedUsageQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
if (!moreResults) {
return Maybe<Usage>(Nothing());
}
QM_TRY_UNWRAP(Usage trackedUsage, stmt.GetUsageByColumn(/* Column */ 0u));
return Some(trackedUsage);
}
Result<bool, nsresult> ScanTrackedFiles(
const FileSystemConnection& aConnection,
const FileSystemFileManager& aFileManager) {
QM_TRY_INSPECT(const nsTArray<EntryId>& trackedFiles,
GetTrackedFiles(aConnection).mapErr(toNSResult));
bool ok = true;
for (const auto& entryId : trackedFiles) {
// On success, tracked is set to false, otherwise its value is kept (= true)
QM_WARNONLY_TRY(MOZ_TO_RESULT(UpdateUsageUnsetTracked(
aConnection, aFileManager, entryId)),
[&ok](const auto& /*aRv*/) { ok = false; });
}
return ok;
}
Result<Ok, QMResult> DeleteEntry(const FileSystemConnection& aConnection,
const EntryId& aEntryId) {
// If it's a directory, deleting the handle will cascade
const nsLiteralCString deleteEntryQuery =
"DELETE FROM Entries "
"WHERE handle = :handle "
";"_ns;
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(aConnection, deleteEntryQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
QM_TRY(QM_TO_RESULT(stmt.Execute()));
return Ok{};
}
nsresult IncreaseCachedQuotaUsage(const FileSystemConnection& aConnection,
int64_t aDelta) {
RefPtr<quota::QuotaObject> quotaObject;
{
RefPtr<quota::QuotaObject> dummy;
QM_TRY(MOZ_TO_RESULT(aConnection->GetQuotaObjects(
getter_AddRefs(quotaObject), getter_AddRefs(dummy))));
}
MOZ_ASSERT(quotaObject);
QM_TRY(OkIf(quotaObject->IncreaseSize(aDelta)),
NS_ERROR_FILE_NO_DEVICE_SPACE);
return NS_OK;
}
Result<int32_t, QMResult> GetTrackedFilesCount(
const FileSystemConnection& aConnection) {
// TODO: We could query the count directly
QM_TRY_INSPECT(const auto& trackedFiles, GetTrackedFiles(aConnection));
CheckedInt32 checkedFileCount = trackedFiles.Length();
QM_TRY(OkIf(checkedFileCount.isValid()),
Err(QMResult(NS_ERROR_ILLEGAL_VALUE)));
return checkedFileCount.value();
}
void LogWithFilename(const FileSystemFileManager& aFileManager,
const char* aFormat, const EntryId& aEntryId) {
if (!LOG_ENABLED()) {
return;
}
QM_TRY_INSPECT(const auto& localFile, aFileManager.GetFile(aEntryId),
QM_VOID);
nsAutoString localPath;
QM_TRY(MOZ_TO_RESULT(localFile->GetPath(localPath)), QM_VOID);
LOG((aFormat, NS_ConvertUTF16toUTF8(localPath).get()));
}
// TODO: Implement idle maintenance
void TryRemoveDuringIdleMaintenance(
const nsTArray<EntryId>& /* aItemToRemove */) {
// Not implemented
}
} // namespace
FileSystemDatabaseManagerVersion001::FileSystemDatabaseManagerVersion001(
FileSystemDataManager* aDataManager, FileSystemConnection&& aConnection,
UniquePtr<FileSystemFileManager>&& aFileManager, const EntryId& aRootEntry)
: mDataManager(aDataManager),
mConnection(aConnection),
mFileManager(std::move(aFileManager)),
mRootEntry(aRootEntry),
mClientMetadata(aDataManager->OriginMetadataRef(),
quota::Client::FILESYSTEM),
mFilesOfUnknownUsage(-1) {}
/* static */
nsresult FileSystemDatabaseManagerVersion001::RescanTrackedUsages(
const FileSystemConnection& aConnection, const Origin& aOrigin) {
QM_TRY_UNWRAP(
FileSystemFileManager fileManager,
data::FileSystemFileManager::CreateFileSystemFileManager(aOrigin));
QM_TRY_UNWRAP(bool ok, ScanTrackedFiles(aConnection, fileManager));
if (ok) {
return NS_OK;
}
// Retry once without explicit delay
QM_TRY_UNWRAP(ok, ScanTrackedFiles(aConnection, fileManager));
if (!ok) {
return NS_ERROR_UNEXPECTED;
}
return NS_OK;
}
/* static */
Result<Usage, QMResult> FileSystemDatabaseManagerVersion001::GetFileUsage(
const FileSystemConnection& aConnection) {
@ -665,6 +978,12 @@ nsresult FileSystemDatabaseManagerVersion001::GetFile(
nsCOMPtr<nsIFile>& aFile) const {
MOZ_ASSERT(!aEntryId.IsEmpty());
const FileSystemEntryPair endPoints(mRootEntry, aEntryId);
QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, endPoints));
if (aPath.IsEmpty()) {
return NS_ERROR_DOM_NOT_FOUND_ERR;
}
QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aEntryId));
QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, aEntryId, aType)));
@ -673,11 +992,6 @@ nsresult FileSystemDatabaseManagerVersion001::GetFile(
QM_TRY(MOZ_TO_RESULT(aFile->GetLastModifiedTime(&lastModTime)));
lastModifiedMilliSeconds = static_cast<TimeStamp>(lastModTime);
FileSystemEntryPair endPoints(mRootEntry, aEntryId);
QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, endPoints));
if (aPath.IsEmpty()) {
return NS_ERROR_DOM_NOT_FOUND_ERR;
}
aPath.Reverse();
return NS_OK;
@ -685,8 +999,6 @@ nsresult FileSystemDatabaseManagerVersion001::GetFile(
nsresult FileSystemDatabaseManagerVersion001::UpdateUsage(
const EntryId& aEntry) {
auto toNSResult = [](const auto& aRv) { return ToNSResult(aRv); };
// We don't track directories or non-existent files.
QM_TRY_UNWRAP(bool fileExists,
DoesFileExist(mConnection, aEntry).mapErr(toNSResult));
@ -712,6 +1024,80 @@ nsresult FileSystemDatabaseManagerVersion001::UpdateUsage(
return NS_OK;
}
nsresult FileSystemDatabaseManagerVersion001::UpdateCachedQuotaUsage(
int64_t aDelta) {
if (0 == aDelta) {
return NS_OK;
}
if (0 < aDelta) {
return IncreaseCachedQuotaUsage(mConnection, aDelta);
}
DecreaseCachedQuotaUsage(-aDelta); // Minus aDelta > 0 of quota free'd
return NS_OK;
}
Result<Ok, QMResult> FileSystemDatabaseManagerVersion001::EnsureUsageIsKnown(
const EntryId& aEntryId) {
if (mFilesOfUnknownUsage < 0) { // Lazy initialization
QM_TRY_UNWRAP(mFilesOfUnknownUsage, GetTrackedFilesCount(mConnection));
}
if (mFilesOfUnknownUsage == 0) {
return Ok{};
}
QM_TRY_UNWRAP(Maybe<Usage> oldUsage,
GetMaybeTrackedUsage(mConnection, aEntryId));
if (oldUsage.isNothing()) {
return Ok{}; // Usage is 0 or it was successfully recorded at unlocking.
}
auto quotaCacheUpdate = [this, oldSize = oldUsage.value()](int64_t aNewSize) {
return UpdateCachedQuotaUsage(aNewSize - oldSize);
};
static const nsLiteralCString updateUsagesKeepTrackedQuery =
"UPDATE Usages SET usage = :usage WHERE handle = :handle;"_ns;
// If usage update fails, we log an error and keep things the way they were.
QM_TRY(QM_TO_RESULT(UpdateUsageForFileEntry(
mConnection, *mFileManager, aEntryId, updateUsagesKeepTrackedQuery,
std::move(quotaCacheUpdate))),
Err(QMResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR)),
([this, &aEntryId](const auto& /*aRv*/) {
LogWithFilename(*mFileManager, "Could not read the size of file %s",
aEntryId);
}));
// We read and updated the quota usage successfully.
--mFilesOfUnknownUsage;
MOZ_ASSERT(mFilesOfUnknownUsage >= 0);
return Ok{};
}
nsresult FileSystemDatabaseManagerVersion001::BeginUsageTracking(
const EntryId& aEntryId) {
MOZ_ASSERT(!aEntryId.IsEmpty());
// If file is already tracked but we cannot read its size, error.
// If file does not exist, this will succeed because usage is zero.
QM_TRY(EnsureUsageIsKnown(aEntryId));
// If file does not exist, set usage tracking to true fails with
// file not found error.
return SetUsageTracking(mConnection, aEntryId, true);
}
nsresult FileSystemDatabaseManagerVersion001::EndUsageTracking(
const EntryId& aEntryId) {
// This is expected to fail only if database is unreachable.
return SetUsageTracking(mConnection, aEntryId, false);
}
Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectory(
const FileSystemChildMetadata& aHandle, bool aRecursive) {
MOZ_ASSERT(!aHandle.parentId().IsEmpty());
@ -726,6 +1112,7 @@ Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectory(
if (aHandle.childName().IsEmpty()) {
return false;
}
DebugOnly<Name> name = aHandle.childName();
MOZ_ASSERT(!name.inspect().IsVoid());
@ -741,57 +1128,34 @@ Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectory(
QM_TRY_UNWRAP(bool isEmpty, IsDirectoryEmpty(mConnection, entryId));
if (!aRecursive && !isEmpty) {
QM_TRY_INSPECT(const nsTArray<EntryId>& descendants,
FindDescendants(mConnection, entryId));
// TODO: This is only done to return the right error for web-compat reasons.
// The spec does not say when the locks need to be checked but wpt tests do.
QM_TRY(OkIf(!isAnyDescendantLocked(descendants)),
Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)));
if (!aRecursive && !isEmpty) {
return Err(QMResult(NS_ERROR_DOM_INVALID_MODIFICATION_ERR));
}
// If it's empty or we can delete recursively, deleting the handle will
// cascade
const nsLiteralCString deleteEntryQuery =
"DELETE FROM Entries "
"WHERE handle = :handle "
";"_ns;
QM_TRY_UNWRAP(Usage usage, GetUsagesOfDescendants(mConnection, entryId));
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
nsTArray<EntryId> removeFails;
QM_TRY_UNWRAP(DebugOnly<Usage> removedUsage,
mFileManager->RemoveFiles(descendants, removeFails));
QM_TRY_INSPECT(const nsTArray<EntryId>& descendants,
FindDescendants(mConnection, entryId));
// We only check the most common case. This can fail spuriously if an external
// application writes to the file, or OS reports zero size due to corruption.
MOZ_ASSERT_IF(removeFails.IsEmpty() && (0 == mFilesOfUnknownUsage),
usage == removedUsage);
QM_TRY(OkIf(!isAnyDescendantLocked(descendants)),
Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)));
TryRemoveDuringIdleMaintenance(removeFails);
{
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, deleteEntryQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
QM_TRY(QM_TO_RESULT(stmt.Execute()));
if (usage > 0) { // Performance!
DecreaseCachedQuotaUsage(usage);
}
QM_TRY(QM_TO_RESULT(transaction.Commit()));
for (const auto& child : descendants) {
QM_WARNONLY_TRY_UNWRAP(const auto maybeFileSize,
mFileManager->RemoveFile(child));
if (maybeFileSize) {
quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
MOZ_ASSERT(quotaManager);
quotaManager->DecreaseUsageForClient(
quota::ClientMetadata{mDataManager->OriginMetadataRef(),
quota::Client::FILESYSTEM},
*maybeFileSize);
}
}
QM_TRY(DeleteEntry(mConnection, entryId));
return true;
}
@ -803,6 +1167,7 @@ Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile(
if (aHandle.childName().IsEmpty()) {
return false;
}
DebugOnly<Name> name = aHandle.childName();
MOZ_ASSERT(!name.inspect().IsVoid());
@ -812,6 +1177,7 @@ Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile(
if (!exists) {
return false;
}
// At this point, entry exists and is a file
QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, true));
MOZ_ASSERT(!entryId.IsEmpty());
@ -825,35 +1191,24 @@ Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile(
return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR));
}
const nsLiteralCString deleteEntryQuery =
"DELETE FROM Entries "
"WHERE handle = :handle "
";"_ns;
mozStorageTransaction transaction(
mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE);
{
QM_TRY_UNWRAP(ResultStatement stmt,
ResultStatement::Create(mConnection, deleteEntryQuery));
QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId)));
QM_TRY(QM_TO_RESULT(stmt.Execute()));
}
QM_TRY(QM_TO_RESULT(transaction.Commit()));
QM_WARNONLY_TRY_UNWRAP(const auto maybeFileSize,
QM_TRY_UNWRAP(Usage usage, GetKnownUsage(mConnection, entryId));
QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> removedUsage,
mFileManager->RemoveFile(entryId));
if (maybeFileSize) {
quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
MOZ_ASSERT(quotaManager);
quotaManager->DecreaseUsageForClient(
quota::ClientMetadata{mDataManager->OriginMetadataRef(),
quota::Client::FILESYSTEM},
*maybeFileSize);
// We only check the most common case. This can fail spuriously if an external
// application writes to the file, or OS reports zero size due to corruption.
MOZ_ASSERT_IF(removedUsage && (0 == mFilesOfUnknownUsage),
usage == removedUsage.value());
if (!removedUsage) {
TryRemoveDuringIdleMaintenance({entryId});
}
if (usage > 0) { // Performance!
DecreaseCachedQuotaUsage(usage);
}
QM_TRY(DeleteEntry(mConnection, entryId));
return true;
}
@ -1031,6 +1386,14 @@ Result<Path, QMResult> FileSystemDatabaseManagerVersion001::Resolve(
void FileSystemDatabaseManagerVersion001::Close() { mConnection->Close(); }
void FileSystemDatabaseManagerVersion001::DecreaseCachedQuotaUsage(
int64_t aDelta) {
quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
MOZ_ASSERT(quotaManager);
quotaManager->DecreaseUsageForClient(mClientMetadata, aDelta);
}
} // namespace fs::data
} // namespace mozilla::dom

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

@ -8,6 +8,7 @@
#define DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION001_H_
#include "FileSystemDatabaseManager.h"
#include "mozilla/dom/quota/CommonMetadata.h"
#include "nsString.h"
namespace mozilla::dom::fs::data {
@ -39,12 +40,13 @@ class FileSystemDatabaseManagerVersion001 : public FileSystemDatabaseManager {
FileSystemDatabaseManagerVersion001(
FileSystemDataManager* aDataManager, FileSystemConnection&& aConnection,
UniquePtr<FileSystemFileManager>&& aFileManager,
const EntryId& aRootEntry)
: mDataManager(aDataManager),
mConnection(aConnection),
mFileManager(std::move(aFileManager)),
mRootEntry(aRootEntry) {}
const EntryId& aRootEntry);
/* Static to allow use by quota client without instantiation */
static nsresult RescanTrackedUsages(const FileSystemConnection& aConnection,
const Origin& aOrigin);
/* Static to allow use by quota client without instantiation */
static Result<Usage, QMResult> GetFileUsage(
const FileSystemConnection& aConnection);
@ -81,11 +83,21 @@ class FileSystemDatabaseManagerVersion001 : public FileSystemDatabaseManager {
virtual void Close() override;
virtual nsresult BeginUsageTracking(const EntryId& aEntryId) override;
virtual nsresult EndUsageTracking(const EntryId& aEntryId) override;
virtual ~FileSystemDatabaseManagerVersion001() = default;
private:
nsresult UpdateUsageInDatabase(const EntryId& aEntry, int64_t aNewDiskUsage);
Result<Ok, QMResult> EnsureUsageIsKnown(const EntryId& aEntryId);
void DecreaseCachedQuotaUsage(int64_t aDelta);
nsresult UpdateCachedQuotaUsage(int64_t aDelta);
// This is a raw pointer since we're owned by the FileSystemDataManager.
FileSystemDataManager* MOZ_NON_OWNING_REF mDataManager;
@ -94,6 +106,10 @@ class FileSystemDatabaseManagerVersion001 : public FileSystemDatabaseManager {
UniquePtr<FileSystemFileManager> mFileManager;
const EntryId mRootEntry;
const quota::ClientMetadata mClientMetadata;
int32_t mFilesOfUnknownUsage;
};
} // namespace mozilla::dom::fs::data

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

@ -16,10 +16,12 @@
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/ResultExtensions.h"
#include "nsCOMPtr.h"
#include "nsHashKeys.h"
#include "nsIFile.h"
#include "nsIFileProtocolHandler.h"
#include "nsIFileURL.h"
#include "nsIURIMutator.h"
#include "nsTHashMap.h"
#include "nsXPCOM.h"
namespace mozilla::dom::fs::data {
@ -109,6 +111,46 @@ Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFile(
return result;
}
nsresult RemoveFileObject(const nsCOMPtr<nsIFile>& aFilePtr) {
// If we cannot tell whether the object is file or directory, or it is a
// directory, it is abandoned as an unknown object. If an attempt is made to
// create a new object with the same path on disk, we regenerate the entryId
// until the collision is resolved.
bool isFile = false;
QM_TRY(MOZ_TO_RESULT(aFilePtr->IsFile(&isFile)));
QM_TRY(OkIf(isFile), NS_ERROR_FILE_IS_DIRECTORY);
QM_TRY(QM_TO_RESULT(aFilePtr->Remove(/* recursive */ false)));
return NS_OK;
}
#ifdef DEBUG
// Unused in release builds
Result<Usage, QMResult> GetFileSize(const nsCOMPtr<nsIFile>& aFileObject) {
bool exists = false;
QM_TRY(QM_TO_RESULT(aFileObject->Exists(&exists)));
if (!exists) {
return 0;
}
bool isFile = false;
QM_TRY(QM_TO_RESULT(aFileObject->IsFile(&isFile)));
// We never create directories with this path: this is an unknown object
// and the file does not exist
QM_TRY(OkIf(isFile), 0);
QM_TRY_UNWRAP(Usage fileSize,
QM_TO_RESULT_INVOKE_MEMBER(aFileObject, GetFileSize));
return fileSize;
}
#endif
} // namespace
Result<nsCOMPtr<nsIFile>, QMResult> GetFileSystemDirectory(
@ -236,7 +278,7 @@ Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetOrCreateFile(
return data::GetOrCreateFile(mTopDirectory, aEntryId);
}
Result<int64_t, QMResult> FileSystemFileManager::RemoveFile(
Result<Usage, QMResult> FileSystemFileManager::RemoveFile(
const EntryId& aEntryId) {
MOZ_ASSERT(!aEntryId.IsEmpty());
QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
@ -252,16 +294,58 @@ Result<int64_t, QMResult> FileSystemFileManager::RemoveFile(
bool isFile = false;
QM_TRY(QM_TO_RESULT(pathObject->IsFile(&isFile)));
// We could handle this also as a nonexistent file.
if (!isFile) {
return Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY));
}
QM_TRY_UNWRAP(int64_t fileSize,
Usage totalUsage = 0;
#ifdef DEBUG
QM_TRY_UNWRAP(totalUsage,
QM_TO_RESULT_INVOKE_MEMBER(pathObject, GetFileSize));
#endif
QM_TRY(QM_TO_RESULT(pathObject->Remove(/* recursive */ false)));
return fileSize;
return totalUsage;
}
Result<DebugOnly<Usage>, QMResult> FileSystemFileManager::RemoveFiles(
const nsTArray<EntryId>& aEntryIds, nsTArray<EntryId>& aRemoveFails) {
if (aEntryIds.IsEmpty()) {
return DebugOnly<Usage>(0);
}
CheckedInt64 totalUsage = 0;
for (const auto& entryId : aEntryIds) {
QM_WARNONLY_TRY_UNWRAP(Maybe<nsCOMPtr<nsIFile>> maybeFile,
GetFileDestination(mTopDirectory, entryId));
if (!maybeFile) {
aRemoveFails.AppendElement(entryId);
continue;
}
nsCOMPtr<nsIFile> fileObject = maybeFile.value();
// Size recorded at close is checked to be equal to the sum of sizes on disk
#ifdef DEBUG
QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> fileSize, GetFileSize(fileObject));
if (!fileSize) {
aRemoveFails.AppendElement(entryId);
continue;
}
totalUsage += fileSize.value();
#endif
QM_WARNONLY_TRY_UNWRAP(Maybe<Ok> ok,
MOZ_TO_RESULT(RemoveFileObject(fileObject)));
if (!ok) {
aRemoveFails.AppendElement(entryId);
}
}
MOZ_ASSERT(totalUsage.isValid());
return DebugOnly<Usage>(totalUsage.value());
}
} // namespace mozilla::dom::fs::data

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

@ -129,11 +129,21 @@ class FileSystemFileManager {
/**
* @brief Remove the disk-backed file object for a specified entry id.
* Note: The returned value is 0 in release builds.
*
* @param aEntryId Specified id of a file system entry
* @return Result<int64_t, QMResult> Error or file size
* @return Result<Usage, QMResult> Error or file size
*/
Result<int64_t, QMResult> RemoveFile(const EntryId& aEntryId);
Result<Usage, QMResult> RemoveFile(const EntryId& aEntryId);
/**
* @brief This method can be used to try to delete a group of files from the
* disk. In debug builds, the sum of the usages is provided ad return value,
* in release builds the sum is not calculated.
* The method attempts to remove all the files requested.
*/
Result<DebugOnly<Usage>, QMResult> RemoveFiles(
const nsTArray<EntryId>& aEntryIds, nsTArray<EntryId>& aRemoveFails);
private:
explicit FileSystemFileManager(nsCOMPtr<nsIFile>&& aTopDirectory);

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

@ -383,8 +383,7 @@ TEST_F(TestFileSystemQuotaClient, WritesToFilesShouldIncreaseUsage) {
PerformOnBackgroundThread(std::move(backgroundTask));
}
TEST_F(TestFileSystemQuotaClient,
DISABLED_TrackedFilesOnInitOriginShouldCauseRescan) {
TEST_F(TestFileSystemQuotaClient, TrackedFilesOnInitOriginShouldCauseRescan) {
auto backgroundTask = []() {
mozilla::Atomic<bool> isCanceled{false};
EntryId* testFileId = new EntryId();
@ -450,7 +449,7 @@ TEST_F(TestFileSystemQuotaClient,
rdm->MutableDatabaseManagerPtr());
// This should force a rescan
ASSERT_TRUE(rdm->LockExclusive(*testFileId));
ASSERT_NSEQ(NS_OK, rdm->LockExclusive(*testFileId));
PerformOnIOThread(std::move(writingToFile), std::move(quotaClient),
rdm->MutableDatabaseManagerPtr());
};