/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "FileSystemFileManager.h" #include "FileSystemDataManager.h" #include "FileSystemHashSource.h" #include "mozilla/Assertions.h" #include "mozilla/NotNull.h" #include "mozilla/Result.h" #include "mozilla/ResultVariant.h" #include "mozilla/Unused.h" #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 { namespace { constexpr nsLiteralString kDatabaseFileName = u"metadata.sqlite"_ns; Result, QMResult> GetFileDestination( const nsCOMPtr& aTopDirectory, const EntryId& aEntryId) { MOZ_ASSERT(32u == aEntryId.Length()); nsCOMPtr destination; // nsIFile Clone is not a constant method QM_TRY(QM_TO_RESULT(aTopDirectory->Clone(getter_AddRefs(destination)))); QM_TRY_UNWRAP(Name encoded, FileSystemHashSource::EncodeHash(aEntryId)); MOZ_ALWAYS_TRUE(IsAscii(encoded)); nsString relativePath; relativePath.Append(Substring(encoded, 0, 2)); QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(relativePath))); QM_TRY(QM_TO_RESULT(destination->AppendRelativePath(encoded))); return destination; } Result, QMResult> GetOrCreateFileImpl( const nsAString& aFilePath) { MOZ_ASSERT(!aFilePath.IsEmpty()); nsCOMPtr result; QM_TRY(QM_TO_RESULT(NS_NewLocalFile(aFilePath, /* aFollowLinks */ false, getter_AddRefs(result)))); bool exists = true; QM_TRY(QM_TO_RESULT(result->Exists(&exists))); if (!exists) { QM_TRY(QM_TO_RESULT(result->Create(nsIFile::NORMAL_FILE_TYPE, 0644))); return result; } bool isDirectory = true; QM_TRY(QM_TO_RESULT(result->IsDirectory(&isDirectory))); QM_TRY(OkIf(!isDirectory), Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY))); return result; } Result, QMResult> GetFile( const nsCOMPtr& aTopDirectory, const EntryId& aEntryId) { MOZ_ASSERT(!aEntryId.IsEmpty()); QM_TRY_UNWRAP(nsCOMPtr pathObject, GetFileDestination(aTopDirectory, aEntryId)); nsString desiredPath; QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath))); nsCOMPtr result; QM_TRY(QM_TO_RESULT(NS_NewLocalFile(desiredPath, /* aFollowLinks */ false, getter_AddRefs(result)))); return result; } Result, QMResult> GetOrCreateFile( const nsCOMPtr& aTopDirectory, const EntryId& aEntryId) { MOZ_ASSERT(!aEntryId.IsEmpty()); QM_TRY_UNWRAP(nsCOMPtr pathObject, GetFileDestination(aTopDirectory, aEntryId)); nsString desiredPath; QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath))); QM_TRY_UNWRAP(nsCOMPtr result, GetOrCreateFileImpl(desiredPath)); return result; } nsresult RemoveFileObject(const nsCOMPtr& 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 GetFileSize(const nsCOMPtr& 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, QMResult> GetFileSystemDirectory( const quota::OriginMetadata& aOriginMetadata) { MOZ_ASSERT(aOriginMetadata.mPersistenceType == quota::PERSISTENCE_TYPE_DEFAULT); quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); MOZ_ASSERT(quotaManager); QM_TRY_UNWRAP(nsCOMPtr fileSystemDirectory, QM_TO_RESULT_TRANSFORM( quotaManager->GetOriginDirectory(aOriginMetadata))); QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath( NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME)))); return fileSystemDirectory; } nsresult EnsureFileSystemDirectory( const quota::OriginMetadata& aOriginMetadata) { quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); MOZ_ASSERT(quotaManager); QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureStorageIsInitialized())); QM_TRY(MOZ_TO_RESULT(quotaManager->EnsureTemporaryStorageIsInitialized())); QM_TRY_INSPECT(const auto& fileSystemDirectory, quotaManager ->EnsureTemporaryOriginIsInitialized( quota::PERSISTENCE_TYPE_DEFAULT, aOriginMetadata) .map([](const auto& aPair) { return aPair.first; })); QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath( NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME)))); bool exists = true; QM_TRY(QM_TO_RESULT(fileSystemDirectory->Exists(&exists))); if (!exists) { QM_TRY(QM_TO_RESULT( fileSystemDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755))); return NS_OK; } bool isDirectory = true; QM_TRY(QM_TO_RESULT(fileSystemDirectory->IsDirectory(&isDirectory))); QM_TRY(OkIf(isDirectory), NS_ERROR_FILE_NOT_DIRECTORY); return NS_OK; } Result, QMResult> GetDatabaseFile( const quota::OriginMetadata& aOriginMetadata) { MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty()); QM_TRY_UNWRAP(nsCOMPtr databaseFile, GetFileSystemDirectory(aOriginMetadata)); QM_TRY(QM_TO_RESULT(databaseFile->AppendRelativePath(kDatabaseFileName))); return databaseFile; } /** * TODO: This is almost identical to the corresponding function of IndexedDB */ Result, QMResult> GetDatabaseFileURL( const quota::OriginMetadata& aOriginMetadata, const int64_t aDirectoryLockId) { MOZ_ASSERT(aDirectoryLockId >= 0); QM_TRY_UNWRAP(nsCOMPtr databaseFile, GetDatabaseFile(aOriginMetadata)); QM_TRY_INSPECT( const auto& protocolHandler, QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( nsCOMPtr, MOZ_SELECT_OVERLOAD(do_GetService), NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "file"))); QM_TRY_INSPECT(const auto& fileHandler, QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( nsCOMPtr, MOZ_SELECT_OVERLOAD(do_QueryInterface), protocolHandler))); QM_TRY_INSPECT(const auto& mutator, QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( nsCOMPtr, fileHandler, NewFileURIMutator, databaseFile))); nsCString directoryLockIdClause = "&directoryLockId="_ns; directoryLockIdClause.AppendInt(aDirectoryLockId); nsCOMPtr result; QM_TRY(QM_TO_RESULT( NS_MutateURI(mutator).SetQuery(directoryLockIdClause).Finalize(result))); return result; } /* static */ Result FileSystemFileManager::CreateFileSystemFileManager( nsCOMPtr&& topDirectory) { return FileSystemFileManager(std::move(topDirectory)); } /* static */ Result FileSystemFileManager::CreateFileSystemFileManager( const quota::OriginMetadata& aOriginMetadata) { QM_TRY_UNWRAP(nsCOMPtr topDirectory, GetFileSystemDirectory(aOriginMetadata)); return FileSystemFileManager(std::move(topDirectory)); } FileSystemFileManager::FileSystemFileManager(nsCOMPtr&& aTopDirectory) : mTopDirectory(std::move(aTopDirectory)) {} Result, QMResult> FileSystemFileManager::GetFile( const EntryId& aEntryId) const { return data::GetFile(mTopDirectory, aEntryId); } Result, QMResult> FileSystemFileManager::GetOrCreateFile( const EntryId& aEntryId) { return data::GetOrCreateFile(mTopDirectory, aEntryId); } Result FileSystemFileManager::RemoveFile( const EntryId& aEntryId) { MOZ_ASSERT(!aEntryId.IsEmpty()); QM_TRY_UNWRAP(nsCOMPtr pathObject, GetFileDestination(mTopDirectory, aEntryId)); bool exists = false; QM_TRY(QM_TO_RESULT(pathObject->Exists(&exists))); if (!exists) { return 0; } 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)); } 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 totalUsage; } Result, QMResult> FileSystemFileManager::RemoveFiles( const nsTArray& aEntryIds, nsTArray& aRemoveFails) { if (aEntryIds.IsEmpty()) { return DebugOnly(0); } CheckedInt64 totalUsage = 0; for (const auto& entryId : aEntryIds) { QM_WARNONLY_TRY_UNWRAP(Maybe> maybeFile, GetFileDestination(mTopDirectory, entryId)); if (!maybeFile) { aRemoveFails.AppendElement(entryId); continue; } nsCOMPtr 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 fileSize, GetFileSize(fileObject)); if (!fileSize) { aRemoveFails.AppendElement(entryId); continue; } totalUsage += fileSize.value(); #endif QM_WARNONLY_TRY_UNWRAP(Maybe ok, MOZ_TO_RESULT(RemoveFileObject(fileObject))); if (!ok) { aRemoveFails.AppendElement(entryId); } } MOZ_ASSERT(totalUsage.isValid()); return DebugOnly(totalUsage.value()); } } // namespace mozilla::dom::fs::data