diff options
Diffstat (limited to 'dom/fs/parent')
32 files changed, 4314 insertions, 0 deletions
diff --git a/dom/fs/parent/FileSystemAccessHandleParent.cpp b/dom/fs/parent/FileSystemAccessHandleParent.cpp new file mode 100644 index 0000000000..1dd6dccb9f --- /dev/null +++ b/dom/fs/parent/FileSystemAccessHandleParent.cpp @@ -0,0 +1,43 @@ +/* -*- 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 "FileSystemAccessHandleParent.h" + +#include "FileSystemDataManager.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemManagerParent.h" + +namespace mozilla::dom { + +FileSystemAccessHandleParent::FileSystemAccessHandleParent( + RefPtr<FileSystemManagerParent> aManager, const fs::EntryId& aEntryId) + : mManager(std::move(aManager)), mEntryId(aEntryId) {} + +FileSystemAccessHandleParent::~FileSystemAccessHandleParent() { + MOZ_ASSERT(mClosed); +} + +mozilla::ipc::IPCResult FileSystemAccessHandleParent::RecvClose() { + Close(); + + return IPC_OK(); +} + +void FileSystemAccessHandleParent::ActorDestroy(ActorDestroyReason aWhy) { + if (!IsClosed()) { + Close(); + } +} + +void FileSystemAccessHandleParent::Close() { + LOG(("Closing SyncAccessHandle")); + + mClosed.Flip(); + + mManager->DataManagerStrongRef()->UnlockExclusive(mEntryId); +} + +} // namespace mozilla::dom diff --git a/dom/fs/parent/FileSystemAccessHandleParent.h b/dom/fs/parent/FileSystemAccessHandleParent.h new file mode 100644 index 0000000000..f1d0917a3a --- /dev/null +++ b/dom/fs/parent/FileSystemAccessHandleParent.h @@ -0,0 +1,45 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_FILESYSTEMACCESSHANDLEPARENT_H_ +#define DOM_FS_PARENT_FILESYSTEMACCESSHANDLEPARENT_H_ + +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/FlippedOnce.h" +#include "mozilla/dom/PFileSystemAccessHandleParent.h" + +namespace mozilla::dom { + +class FileSystemManagerParent; + +class FileSystemAccessHandleParent : public PFileSystemAccessHandleParent { + public: + FileSystemAccessHandleParent(RefPtr<FileSystemManagerParent> aManager, + const fs::EntryId& aEntryId); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemAccessHandleParent, override) + + mozilla::ipc::IPCResult RecvClose(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + virtual ~FileSystemAccessHandleParent(); + + bool IsClosed() const { return mClosed; } + + void Close(); + + const RefPtr<FileSystemManagerParent> mManager; + + const fs::EntryId mEntryId; + + FlippedOnce<false> mClosed; +}; + +} // namespace mozilla::dom + +#endif // DOM_FS_PARENT_FILESYSTEMACCESSHANDLEPARENT_H_ diff --git a/dom/fs/parent/FileSystemHashSource.cpp b/dom/fs/parent/FileSystemHashSource.cpp new file mode 100644 index 0000000000..eb43575371 --- /dev/null +++ b/dom/fs/parent/FileSystemHashSource.cpp @@ -0,0 +1,67 @@ +/* -*- 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 "FileSystemHashSource.h" + +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/data_encoding_ffi_generated.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "nsComponentManagerUtils.h" +#include "nsICryptoHash.h" +#include "nsNetCID.h" +#include "nsString.h" +#include "nsStringFwd.h" + +namespace mozilla::dom::fs::data { + +Result<EntryId, QMResult> FileSystemHashSource::GenerateHash( + const EntryId& aParent, const Name& aName) { + auto makeHasher = [](nsresult* aRv) { + return do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, aRv); + }; + QM_TRY_INSPECT(const auto& hasher, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( + nsCOMPtr<nsICryptoHash>, makeHasher))); + + QM_TRY(QM_TO_RESULT(hasher->Init(nsICryptoHash::SHA256))); + + QM_TRY(QM_TO_RESULT( + hasher->Update(reinterpret_cast<const uint8_t*>(aName.BeginReading()), + sizeof(char16_t) * aName.Length()))); + + QM_TRY(QM_TO_RESULT( + hasher->Update(reinterpret_cast<const uint8_t*>(aParent.BeginReading()), + aParent.Length()))); + + EntryId entryId; + QM_TRY(QM_TO_RESULT(hasher->Finish(/* aASCII */ false, entryId))); + MOZ_ASSERT(!entryId.IsEmpty()); + + return entryId; +} + +Result<Name, QMResult> FileSystemHashSource::EncodeHash( + const EntryId& aEntryId) { + MOZ_ASSERT(32u == aEntryId.Length()); + nsCString encoded; + base32encode(&aEntryId, &encoded); + + // We are stripping last four padding characters because + // it may not be allowed in some file systems. + MOZ_ASSERT(56u == encoded.Length() && '=' == encoded[52u] && + '=' == encoded[53u] && '=' == encoded[54u] && '=' == encoded[55u]); + encoded.SetLength(52u); + + Name result; + QM_TRY(OkIf(result.SetCapacity(encoded.Length(), mozilla::fallible)), + Err(QMResult(NS_ERROR_OUT_OF_MEMORY))); + + result.AppendASCII(encoded); + + return result; +} + +} // namespace mozilla::dom::fs::data diff --git a/dom/fs/parent/FileSystemHashSource.h b/dom/fs/parent/FileSystemHashSource.h new file mode 100644 index 0000000000..2b7ca0136f --- /dev/null +++ b/dom/fs/parent/FileSystemHashSource.h @@ -0,0 +1,26 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_FILESYSTEMHASHSOURCE_H_ +#define DOM_FS_PARENT_FILESYSTEMHASHSOURCE_H_ + +#include "mozilla/Result.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/QMResult.h" +#include "mozilla/dom/quota/ResultExtensions.h" + +namespace mozilla::dom::fs::data { + +struct FileSystemHashSource { + static Result<EntryId, QMResult> GenerateHash(const EntryId& aParent, + const Name& aName); + + static Result<Name, QMResult> EncodeHash(const EntryId& aEntryId); +}; + +} // namespace mozilla::dom::fs::data + +#endif // DOM_FS_PARENT_FILESYSTEMHASHSOURCE_H_ diff --git a/dom/fs/parent/FileSystemManagerParent.cpp b/dom/fs/parent/FileSystemManagerParent.cpp new file mode 100644 index 0000000000..7f53346893 --- /dev/null +++ b/dom/fs/parent/FileSystemManagerParent.cpp @@ -0,0 +1,521 @@ +/* -*- 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 "FileSystemManagerParent.h" + +#include "FileSystemDatabaseManager.h" +#include "FileSystemStreamCallbacks.h" +#include "mozilla/Maybe.h" +#include "mozilla/dom/FileBlobImpl.h" +#include "mozilla/dom/FileSystemAccessHandleParent.h" +#include "mozilla/dom/FileSystemDataManager.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/FileSystemWritableFileStreamParent.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/QMResult.h" +#include "mozilla/dom/quota/FileStreams.h" +#include "mozilla/dom/quota/ForwardDecls.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "mozilla/ipc/FileDescriptorUtils.h" +#include "mozilla/ipc/RandomAccessStreamUtils.h" +#include "nsNetUtil.h" +#include "nsString.h" +#include "nsTArray.h" + +using IPCResult = mozilla::ipc::IPCResult; + +namespace mozilla::dom { + +FileSystemManagerParent::FileSystemManagerParent( + RefPtr<fs::data::FileSystemDataManager> aDataManager, + const EntryId& aRootEntry) + : mDataManager(std::move(aDataManager)), mRootResponse(aRootEntry) {} + +FileSystemManagerParent::~FileSystemManagerParent() { + LOG(("Destroying FileSystemManagerParent %p", this)); +} + +void FileSystemManagerParent::AssertIsOnIOTarget() const { + MOZ_ASSERT(mDataManager); + + mDataManager->AssertIsOnIOTarget(); +} + +const RefPtr<fs::data::FileSystemDataManager>& +FileSystemManagerParent::DataManagerStrongRef() const { + MOZ_ASSERT(!mActorDestroyed); + MOZ_ASSERT(mDataManager); + + return mDataManager; +} + +IPCResult FileSystemManagerParent::RecvGetRootHandle( + GetRootHandleResolver&& aResolver) { + AssertIsOnIOTarget(); + + aResolver(mRootResponse); + + return IPC_OK(); +} + +IPCResult FileSystemManagerParent::RecvGetDirectoryHandle( + FileSystemGetHandleRequest&& aRequest, + GetDirectoryHandleResolver&& aResolver) { + LOG(("GetDirectoryHandle %s ", + NS_ConvertUTF16toUTF8(aRequest.handle().childName()).get())); + AssertIsOnIOTarget(); + MOZ_ASSERT(!aRequest.handle().parentId().IsEmpty()); + MOZ_ASSERT(mDataManager); + + auto reportError = [&aResolver](const QMResult& aRv) { + FileSystemGetHandleResponse response(ToNSResult(aRv)); + aResolver(response); + }; + + QM_TRY_UNWRAP(fs::EntryId entryId, + mDataManager->MutableDatabaseManagerPtr()->GetOrCreateDirectory( + aRequest.handle(), aRequest.create()), + IPC_OK(), reportError); + MOZ_ASSERT(!entryId.IsEmpty()); + + FileSystemGetHandleResponse response(entryId); + aResolver(response); + + return IPC_OK(); +} + +IPCResult FileSystemManagerParent::RecvGetFileHandle( + FileSystemGetHandleRequest&& aRequest, GetFileHandleResolver&& aResolver) { + AssertIsOnIOTarget(); + MOZ_ASSERT(!aRequest.handle().parentId().IsEmpty()); + MOZ_ASSERT(mDataManager); + + auto reportError = [&aResolver](const QMResult& aRv) { + FileSystemGetHandleResponse response(ToNSResult(aRv)); + aResolver(response); + }; + + QM_TRY_UNWRAP(fs::EntryId entryId, + mDataManager->MutableDatabaseManagerPtr()->GetOrCreateFile( + aRequest.handle(), aRequest.create()), + IPC_OK(), reportError); + MOZ_ASSERT(!entryId.IsEmpty()); + + FileSystemGetHandleResponse response(entryId); + aResolver(response); + return IPC_OK(); +} + +// Could use a template, but you need several types +mozilla::ipc::IPCResult FileSystemManagerParent::RecvGetAccessHandle( + FileSystemGetAccessHandleRequest&& aRequest, + GetAccessHandleResolver&& aResolver) { + AssertIsOnIOTarget(); + MOZ_ASSERT(mDataManager); + + if (!mDataManager->LockExclusive(aRequest.entryId())) { + aResolver(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return IPC_OK(); + } + + auto autoUnlock = + MakeScopeExit([self = RefPtr<FileSystemManagerParent>(this), aRequest] { + self->mDataManager->UnlockExclusive(aRequest.entryId()); + }); + + auto reportError = [aResolver](nsresult rv) { aResolver(rv); }; + + nsString type; + fs::TimeStamp lastModifiedMilliSeconds; + fs::Path path; + nsCOMPtr<nsIFile> file; + QM_TRY(MOZ_TO_RESULT(mDataManager->MutableDatabaseManagerPtr()->GetFile( + aRequest.entryId(), type, lastModifiedMilliSeconds, path, file)), + IPC_OK(), reportError); + + if (LOG_ENABLED()) { + nsAutoString path; + if (NS_SUCCEEDED(file->GetPath(path))) { + LOG(("Opening SyncAccessHandle %s", NS_ConvertUTF16toUTF8(path).get())); + } + } + + QM_TRY_UNWRAP( + nsCOMPtr<nsIRandomAccessStream> stream, + CreateFileRandomAccessStream(quota::PERSISTENCE_TYPE_DEFAULT, + mDataManager->OriginMetadataRef(), + quota::Client::FILESYSTEM, file, -1, -1, + nsIFileRandomAccessStream::DEFER_OPEN), + IPC_OK(), reportError); + + EnsureStreamCallbacks(); + + RandomAccessStreamParams streamParams = + mozilla::ipc::SerializeRandomAccessStream( + WrapMovingNotNullUnchecked(std::move(stream)), mStreamCallbacks); + + auto accessHandleParent = + MakeRefPtr<FileSystemAccessHandleParent>(this, aRequest.entryId()); + + // Release the auto unlock helper just before calling + // SendPFileSystemAccessHandleConstructor which is responsible for destroying + // the actor if the sending fails (we call `UnlockExclusive` when the actor is + // destroyed). + autoUnlock.release(); + + if (!SendPFileSystemAccessHandleConstructor(accessHandleParent)) { + aResolver(NS_ERROR_FAILURE); + return IPC_OK(); + } + + aResolver(FileSystemAccessHandleProperties(std::move(streamParams), + accessHandleParent, nullptr)); + return IPC_OK(); +} + +mozilla::ipc::IPCResult FileSystemManagerParent::RecvGetWritable( + FileSystemGetWritableRequest&& aRequest, GetWritableResolver&& aResolver) { + AssertIsOnIOTarget(); + MOZ_ASSERT(mDataManager); + + if (!mDataManager->LockShared(aRequest.entryId())) { + aResolver(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR); + return IPC_OK(); + } + + auto autoUnlock = + MakeScopeExit([self = RefPtr<FileSystemManagerParent>(this), aRequest] { + self->mDataManager->UnlockShared(aRequest.entryId()); + }); + + auto reportError = [aResolver](nsresult rv) { aResolver(rv); }; + + nsString type; + fs::TimeStamp lastModifiedMilliSeconds; + fs::Path path; + nsCOMPtr<nsIFile> file; + QM_TRY(MOZ_TO_RESULT(mDataManager->MutableDatabaseManagerPtr()->GetFile( + aRequest.entryId(), type, lastModifiedMilliSeconds, path, file)), + IPC_OK(), reportError); + + if (LOG_ENABLED()) { + nsAutoString path; + if (NS_SUCCEEDED(file->GetPath(path))) { + LOG(("Opening Writable %s", NS_ConvertUTF16toUTF8(path).get())); + } + } + + FILE* fileHandle; + QM_TRY(MOZ_TO_RESULT(file->OpenANSIFileDesc(aRequest.keepData() ? "r+" : "w", + &fileHandle)), + IPC_OK(), reportError); + + auto autoClose = MakeScopeExit([fileHandle]() { + QM_WARNONLY_TRY(MOZ_TO_RESULT(0 == fclose(fileHandle))); + }); + + FileDescriptor fileDescriptor = + mozilla::ipc::FILEToFileDescriptor(fileHandle); + + LOG(("Opened")); + + auto writableFileStreamParent = + MakeRefPtr<FileSystemWritableFileStreamParent>(this, aRequest.entryId()); + + // Release the auto unlock helper just before calling + // SendPFileSystemWritableFileStreamConstructor which is responsible for + // destroying the actor if the sending fails (we call `UnlockExclusive` when + // the actor is destroyed). + autoUnlock.release(); + + if (!SendPFileSystemWritableFileStreamConstructor(writableFileStreamParent)) { + aResolver(NS_ERROR_FAILURE); + return IPC_OK(); + } + + aResolver(FileSystemWritableFileStreamProperties( + fileDescriptor, writableFileStreamParent, nullptr)); + return IPC_OK(); +} + +IPCResult FileSystemManagerParent::RecvGetFile( + FileSystemGetFileRequest&& aRequest, GetFileResolver&& aResolver) { + AssertIsOnIOTarget(); + + // XXX Spec https://www.w3.org/TR/FileAPI/#dfn-file wants us to snapshot the + // state of the file at getFile() time + + // You can create a File with getFile() even if the file is locked + // XXX factor out this part of the code for accesshandle/ and getfile + auto reportError = [aResolver](nsresult rv) { + LOG(("getFile() Failed!")); + aResolver(rv); + }; + + nsString type; + fs::TimeStamp lastModifiedMilliSeconds; + fs::Path path; + nsCOMPtr<nsIFile> fileObject; + QM_TRY(MOZ_TO_RESULT(mDataManager->MutableDatabaseManagerPtr()->GetFile( + aRequest.entryId(), type, lastModifiedMilliSeconds, path, + fileObject)), + IPC_OK(), reportError); + + if (LOG_ENABLED()) { + nsAutoString path; + if (NS_SUCCEEDED(fileObject->GetPath(path))) { + LOG(("Opening File as blob: %s", NS_ConvertUTF16toUTF8(path).get())); + } + } + + RefPtr<BlobImpl> blob = MakeRefPtr<FileBlobImpl>(fileObject); + + IPCBlob ipcBlob; + QM_TRY(MOZ_TO_RESULT(IPCBlobUtils::Serialize(blob, ipcBlob)), IPC_OK(), + reportError); + + aResolver( + FileSystemFileProperties(lastModifiedMilliSeconds, ipcBlob, type, path)); + return IPC_OK(); +} + +IPCResult FileSystemManagerParent::RecvResolve( + FileSystemResolveRequest&& aRequest, ResolveResolver&& aResolver) { + AssertIsOnIOTarget(); + MOZ_ASSERT(!aRequest.endpoints().parentId().IsEmpty()); + MOZ_ASSERT(!aRequest.endpoints().childId().IsEmpty()); + MOZ_ASSERT(mDataManager); + + fs::Path filePath; + if (aRequest.endpoints().parentId() == aRequest.endpoints().childId()) { + FileSystemResolveResponse response(Some(FileSystemPath(filePath))); + aResolver(response); + + return IPC_OK(); + } + + auto reportError = [&aResolver](const QMResult& aRv) { + FileSystemResolveResponse response(ToNSResult(aRv)); + aResolver(response); + }; + + QM_TRY_UNWRAP( + filePath, + mDataManager->MutableDatabaseManagerPtr()->Resolve(aRequest.endpoints()), + IPC_OK(), reportError); + + if (LOG_ENABLED()) { + nsString path; + for (auto& entry : filePath) { + path.Append(entry); + } + LOG(("Resolve path: %s", NS_ConvertUTF16toUTF8(path).get())); + } + + if (filePath.IsEmpty()) { + FileSystemResolveResponse response(Nothing{}); + aResolver(response); + + return IPC_OK(); + } + + FileSystemResolveResponse response(Some(FileSystemPath(filePath))); + aResolver(response); + + return IPC_OK(); +} + +IPCResult FileSystemManagerParent::RecvGetEntries( + FileSystemGetEntriesRequest&& aRequest, GetEntriesResolver&& aResolver) { + AssertIsOnIOTarget(); + MOZ_ASSERT(!aRequest.parentId().IsEmpty()); + MOZ_ASSERT(mDataManager); + + auto reportError = [&aResolver](const QMResult& aRv) { + FileSystemGetEntriesResponse response(ToNSResult(aRv)); + aResolver(response); + }; + + QM_TRY_UNWRAP(FileSystemDirectoryListing entries, + mDataManager->MutableDatabaseManagerPtr()->GetDirectoryEntries( + aRequest.parentId(), aRequest.page()), + IPC_OK(), reportError); + + FileSystemGetEntriesResponse response(entries); + aResolver(response); + + return IPC_OK(); +} + +IPCResult FileSystemManagerParent::RecvRemoveEntry( + FileSystemRemoveEntryRequest&& aRequest, RemoveEntryResolver&& aResolver) { + LOG(("RemoveEntry %s", + NS_ConvertUTF16toUTF8(aRequest.handle().childName()).get())); + AssertIsOnIOTarget(); + MOZ_ASSERT(!aRequest.handle().parentId().IsEmpty()); + MOZ_ASSERT(mDataManager); + + auto reportError = [&aResolver](const QMResult& aRv) { + FileSystemRemoveEntryResponse response(ToNSResult(aRv)); + aResolver(response); + }; + + QM_TRY_UNWRAP( + bool isDeleted, + mDataManager->MutableDatabaseManagerPtr()->RemoveFile(aRequest.handle()), + IPC_OK(), reportError); + + if (isDeleted) { + FileSystemRemoveEntryResponse response(NS_OK); + aResolver(response); + + return IPC_OK(); + } + + QM_TRY_UNWRAP(isDeleted, + mDataManager->MutableDatabaseManagerPtr()->RemoveDirectory( + aRequest.handle(), aRequest.recursive()), + IPC_OK(), reportError); + + if (!isDeleted) { + FileSystemRemoveEntryResponse response(NS_ERROR_DOM_NOT_FOUND_ERR); + aResolver(response); + + return IPC_OK(); + } + + FileSystemRemoveEntryResponse response(NS_OK); + aResolver(response); + + return IPC_OK(); +} + +IPCResult FileSystemManagerParent::RecvMoveEntry( + FileSystemMoveEntryRequest&& aRequest, MoveEntryResolver&& aResolver) { + LOG(("MoveEntry %s to %s", + NS_ConvertUTF16toUTF8(aRequest.handle().entryName()).get(), + NS_ConvertUTF16toUTF8(aRequest.destHandle().childName()).get())); + MOZ_ASSERT(!aRequest.handle().entryId().IsEmpty()); + MOZ_ASSERT(!aRequest.destHandle().parentId().IsEmpty()); + MOZ_ASSERT(mDataManager); + + auto reportError = [&aResolver](const QMResult& aRv) { + FileSystemMoveEntryResponse response(ToNSResult(aRv)); + aResolver(response); + }; + + QM_TRY_UNWRAP(bool moved, + mDataManager->MutableDatabaseManagerPtr()->MoveEntry( + aRequest.handle(), aRequest.destHandle()), + IPC_OK(), reportError); + + fs::FileSystemMoveEntryResponse response(moved ? NS_OK : NS_ERROR_FAILURE); + aResolver(response); + return IPC_OK(); +} + +IPCResult FileSystemManagerParent::RecvRenameEntry( + FileSystemRenameEntryRequest&& aRequest, MoveEntryResolver&& aResolver) { + // if destHandle's parentId is empty, then we're renaming in the same + // directory + LOG(("RenameEntry %s to %s", + NS_ConvertUTF16toUTF8(aRequest.handle().entryName()).get(), + NS_ConvertUTF16toUTF8(aRequest.name()).get())); + MOZ_ASSERT(!aRequest.handle().entryId().IsEmpty()); + MOZ_ASSERT(mDataManager); + + auto reportError = [&aResolver](const QMResult& aRv) { + FileSystemMoveEntryResponse response(ToNSResult(aRv)); + aResolver(response); + }; + + QM_TRY_UNWRAP(bool moved, + mDataManager->MutableDatabaseManagerPtr()->RenameEntry( + aRequest.handle(), aRequest.name()), + IPC_OK(), reportError); + + fs::FileSystemMoveEntryResponse response(moved ? NS_OK : NS_ERROR_FAILURE); + aResolver(response); + return IPC_OK(); +} + +IPCResult FileSystemManagerParent::RecvNeedQuota( + FileSystemQuotaRequest&& aRequest, NeedQuotaResolver&& aResolver) { + AssertIsOnIOTarget(); + + aResolver(0u); + + return IPC_OK(); +} + +void FileSystemManagerParent::RequestAllowToClose() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + if (mRequestedAllowToClose) { + return; + } + + mRequestedAllowToClose.Flip(); + + InvokeAsync(mDataManager->MutableIOTargetPtr(), __func__, + [self = RefPtr<FileSystemManagerParent>(this)]() { + return self->SendCloseAll(); + }) + ->Then(mDataManager->MutableIOTargetPtr(), __func__, + [self = RefPtr<FileSystemManagerParent>(this)]( + const CloseAllPromise::ResolveOrRejectValue& aValue) { + self->Close(); + + return BoolPromise::CreateAndResolve(true, __func__); + }); +} + +void FileSystemManagerParent::ActorDestroy(ActorDestroyReason aWhy) { + AssertIsOnIOTarget(); + MOZ_ASSERT(!mActorDestroyed); + + mActorDestroyed = true; + + if (!mStreamCallbacks || mStreamCallbacks->HasNoRemoteQuotaObjectParents()) { + CleanupAfterClose(); + } +} + +void FileSystemManagerParent::EnsureStreamCallbacks() { + if (mStreamCallbacks) { + return; + } + + mStreamCallbacks = MakeRefPtr<FileSystemStreamCallbacks>(); + + mStreamCallbacks->SetRemoteQuotaObjectParentCallback([self = RefPtr(this)]() { + if (self->mActorDestroyed) { + self->CleanupAfterClose(); + } + }); +} + +void FileSystemManagerParent::CleanupAfterClose() { + MOZ_ASSERT(mActorDestroyed); + MOZ_ASSERT_IF(mStreamCallbacks, + mStreamCallbacks->HasNoRemoteQuotaObjectParents()); + + mStreamCallbacks = nullptr; + + InvokeAsync(mDataManager->MutableBackgroundTargetPtr(), __func__, + [self = RefPtr<FileSystemManagerParent>(this)]() { + self->mDataManager->UnregisterActor(WrapNotNull(self)); + + self->mDataManager = nullptr; + + return BoolPromise::CreateAndResolve(true, __func__); + }); +} + +} // namespace mozilla::dom diff --git a/dom/fs/parent/FileSystemManagerParent.h b/dom/fs/parent/FileSystemManagerParent.h new file mode 100644 index 0000000000..98931e79af --- /dev/null +++ b/dom/fs/parent/FileSystemManagerParent.h @@ -0,0 +1,97 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_FILESYSTEMMANAGERPARENT_H_ +#define DOM_FS_PARENT_FILESYSTEMMANAGERPARENT_H_ + +#include "ErrorList.h" +#include "mozilla/dom/FlippedOnce.h" +#include "mozilla/dom/PFileSystemManagerParent.h" +#include "nsISupports.h" + +namespace mozilla::dom { + +class FileSystemStreamCallbacks; + +namespace fs::data { +class FileSystemDataManager; +} // namespace fs::data + +class FileSystemManagerParent : public PFileSystemManagerParent { + public: + FileSystemManagerParent(RefPtr<fs::data::FileSystemDataManager> aDataManager, + const EntryId& aRootEntry); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemManagerParent, override) + + void AssertIsOnIOTarget() const; + + // Safe to call while the actor is live. + const RefPtr<fs::data::FileSystemDataManager>& DataManagerStrongRef() const; + + mozilla::ipc::IPCResult RecvGetRootHandle(GetRootHandleResolver&& aResolver); + + mozilla::ipc::IPCResult RecvGetDirectoryHandle( + FileSystemGetHandleRequest&& aRequest, + GetDirectoryHandleResolver&& aResolver); + + mozilla::ipc::IPCResult RecvGetFileHandle( + FileSystemGetHandleRequest&& aRequest, GetFileHandleResolver&& aResolver); + + mozilla::ipc::IPCResult RecvGetAccessHandle( + FileSystemGetAccessHandleRequest&& aRequest, + GetAccessHandleResolver&& aResolver); + + mozilla::ipc::IPCResult RecvGetWritable( + FileSystemGetWritableRequest&& aRequest, GetWritableResolver&& aResolver); + + mozilla::ipc::IPCResult RecvGetFile(FileSystemGetFileRequest&& aRequest, + GetFileResolver&& aResolver); + + mozilla::ipc::IPCResult RecvResolve(FileSystemResolveRequest&& aRequest, + ResolveResolver&& aResolver); + + mozilla::ipc::IPCResult RecvGetEntries(FileSystemGetEntriesRequest&& aRequest, + GetEntriesResolver&& aResolver); + + mozilla::ipc::IPCResult RecvRemoveEntry( + FileSystemRemoveEntryRequest&& aRequest, RemoveEntryResolver&& aResolver); + + mozilla::ipc::IPCResult RecvMoveEntry(FileSystemMoveEntryRequest&& aRequest, + MoveEntryResolver&& aResolver); + + mozilla::ipc::IPCResult RecvRenameEntry( + FileSystemRenameEntryRequest&& aRequest, MoveEntryResolver&& aResolver); + + mozilla::ipc::IPCResult RecvNeedQuota(FileSystemQuotaRequest&& aRequest, + NeedQuotaResolver&& aResolver); + + void RequestAllowToClose(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + protected: + virtual ~FileSystemManagerParent(); + + private: + void EnsureStreamCallbacks(); + + void CleanupAfterClose(); + + RefPtr<fs::data::FileSystemDataManager> mDataManager; + + RefPtr<FileSystemStreamCallbacks> mStreamCallbacks; + + FileSystemGetHandleResponse mRootResponse; + + FlippedOnce<false> mRequestedAllowToClose; + + bool mActorDestroyed = false; +}; + +} // namespace mozilla::dom + +#endif // DOM_FS_PARENT_FILESYSTEMMANAGERPARENT_H_ diff --git a/dom/fs/parent/FileSystemManagerParentFactory.cpp b/dom/fs/parent/FileSystemManagerParentFactory.cpp new file mode 100644 index 0000000000..1da98a7bc5 --- /dev/null +++ b/dom/fs/parent/FileSystemManagerParentFactory.cpp @@ -0,0 +1,105 @@ +/* -*- 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 "FileSystemManagerParentFactory.h" + +#include "mozilla/OriginAttributes.h" +#include "mozilla/StaticPrefs_dom.h" +#include "mozilla/dom/FileSystemDataManager.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemManagerParent.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/ipc/Endpoint.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsString.h" + +namespace mozilla::dom { +mozilla::ipc::IPCResult CreateFileSystemManagerParent( + const mozilla::ipc::PrincipalInfo& aPrincipalInfo, + mozilla::ipc::Endpoint<PFileSystemManagerParent>&& aParentEndpoint, + std::function<void(const nsresult&)>&& aResolver) { + using CreateActorPromise = + MozPromise<RefPtr<FileSystemManagerParent>, nsresult, true>; + + QM_TRY(OkIf(StaticPrefs::dom_fs_enabled()), IPC_OK(), + [aResolver](const auto&) { aResolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); }); + + QM_TRY(OkIf(aParentEndpoint.IsValid()), IPC_OK(), + [aResolver](const auto&) { aResolver(NS_ERROR_INVALID_ARG); }); + + // This blocks Null and Expanded principals + QM_TRY(OkIf(quota::QuotaManager::IsPrincipalInfoValid(aPrincipalInfo)), + IPC_OK(), + [aResolver](const auto&) { aResolver(NS_ERROR_DOM_SECURITY_ERR); }); + + quota::OriginMetadata originMetadata( + quota::QuotaManager::GetInfoFromValidatedPrincipalInfo(aPrincipalInfo), + quota::PERSISTENCE_TYPE_DEFAULT); + + // Block use for now in PrivateBrowsing + QM_TRY(OkIf(!OriginAttributes::IsPrivateBrowsing(originMetadata.mOrigin)), + IPC_OK(), + [aResolver](const auto&) { aResolver(NS_ERROR_DOM_NOT_ALLOWED_ERR); }); + + LOG(("CreateFileSystemManagerParent, origin: %s", + originMetadata.mOrigin.get())); + + // This creates the file system data manager, which has to be done on + // PBackground + fs::data::FileSystemDataManager::GetOrCreateFileSystemDataManager( + originMetadata) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [origin = originMetadata.mOrigin, + parentEndpoint = std::move(aParentEndpoint), + aResolver](const fs::Registered<fs::data::FileSystemDataManager>& + dataManager) mutable { + QM_TRY_UNWRAP( + fs::EntryId rootId, fs::data::GetRootHandle(origin), QM_VOID, + [aResolver](const auto& aRv) { aResolver(ToNSResult(aRv)); }); + + InvokeAsync( + dataManager->MutableIOTargetPtr(), __func__, + [dataManager = + RefPtr<fs::data::FileSystemDataManager>(dataManager), + rootId, parentEndpoint = std::move(parentEndpoint)]() mutable { + RefPtr<FileSystemManagerParent> parent = + new FileSystemManagerParent(std::move(dataManager), + rootId); + + LOG(("Binding parent endpoint")); + if (!parentEndpoint.Bind(parent)) { + return CreateActorPromise::CreateAndReject(NS_ERROR_FAILURE, + __func__); + } + + return CreateActorPromise::CreateAndResolve(std::move(parent), + __func__); + }) + ->Then(GetCurrentSerialEventTarget(), __func__, + [dataManager = dataManager, aResolver]( + CreateActorPromise::ResolveOrRejectValue&& aValue) { + if (aValue.IsReject()) { + aResolver(aValue.RejectValue()); + } else { + RefPtr<FileSystemManagerParent> parent = + std::move(aValue.ResolveValue()); + + dataManager->RegisterActor(WrapNotNull(parent)); + + aResolver(NS_OK); + } + }); + }, + [aResolver](nsresult aRejectValue) { aResolver(aRejectValue); }); + + return IPC_OK(); +} + +} // namespace mozilla::dom diff --git a/dom/fs/parent/FileSystemManagerParentFactory.h b/dom/fs/parent/FileSystemManagerParentFactory.h new file mode 100644 index 0000000000..6d581ffa3f --- /dev/null +++ b/dom/fs/parent/FileSystemManagerParentFactory.h @@ -0,0 +1,39 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_FILESYSTEMMANAGER_H_ +#define DOM_FS_PARENT_FILESYSTEMMANAGER_H_ + +#include <functional> + +enum class nsresult : uint32_t; + +namespace mozilla { + +namespace ipc { + +template <class T> +class Endpoint; + +class IPCResult; +class PrincipalInfo; + +} // namespace ipc + +namespace dom { + +class PFileSystemManagerParent; + +mozilla::ipc::IPCResult CreateFileSystemManagerParent( + const mozilla::ipc::PrincipalInfo& aPrincipalInfo, + mozilla::ipc::Endpoint<mozilla::dom::PFileSystemManagerParent>&& + aParentEndpoint, + std::function<void(const nsresult&)>&& aResolver); + +} // namespace dom +} // namespace mozilla + +#endif // DOM_FS_PARENT_FILESYSTEMMANAGER_H_ diff --git a/dom/fs/parent/FileSystemQuotaClient.cpp b/dom/fs/parent/FileSystemQuotaClient.cpp new file mode 100644 index 0000000000..6c0271060f --- /dev/null +++ b/dom/fs/parent/FileSystemQuotaClient.cpp @@ -0,0 +1,235 @@ +/* -*- 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 "FileSystemQuotaClient.h" + +#include "ResultStatement.h" +#include "datamodel/FileSystemDatabaseManager.h" +#include "datamodel/FileSystemFileManager.h" +#include "mozIStorageService.h" +#include "mozStorageCID.h" +#include "mozilla/dom/FileSystemDataManager.h" +#include "mozilla/dom/quota/Assertions.h" +#include "mozilla/dom/quota/Client.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/dom/quota/UsageInfo.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "nsIFile.h" + +namespace mozilla::dom::fs { + +namespace { + +auto toNSResult = [](const auto& aRv) { return ToNSResult(aRv); }; + +class QuotaClient final : public mozilla::dom::quota::Client { + public: + QuotaClient(); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::fs::QuotaClient, override) + + Type GetType() override; + + Result<quota::UsageInfo, nsresult> InitOrigin( + quota::PersistenceType aPersistenceType, + const quota::OriginMetadata& aOriginMetadata, + const AtomicBool& aCanceled) override; + + nsresult InitOriginWithoutTracking( + quota::PersistenceType aPersistenceType, + const quota::OriginMetadata& aOriginMetadata, + const AtomicBool& aCanceled) override; + + Result<quota::UsageInfo, nsresult> GetUsageForOrigin( + quota::PersistenceType aPersistenceType, + const quota::OriginMetadata& aOriginMetadata, + const AtomicBool& aCanceled) override; + + void OnOriginClearCompleted(quota::PersistenceType aPersistenceType, + const nsACString& aOrigin) override; + + void ReleaseIOThreadObjects() override; + + void AbortOperationsForLocks( + const DirectoryLockIdTable& aDirectoryLockIds) override; + + void AbortOperationsForProcess(ContentParentId aContentParentId) override; + + void AbortAllOperations() override; + + void StartIdleMaintenance() override; + + void StopIdleMaintenance() override; + + private: + ~QuotaClient() = default; + + void InitiateShutdown() override; + bool IsShutdownCompleted() const override; + nsCString GetShutdownStatus() const override; + void ForceKillActors() override; + void FinalizeShutdown() override; +}; + +Result<ResultConnection, QMResult> GetStorageConnection(const Origin& aOrigin) { + QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& databaseFile, + data::GetDatabaseFile(aOrigin)); + + QM_TRY_INSPECT( + const auto& storageService, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( + nsCOMPtr<mozIStorageService>, MOZ_SELECT_OVERLOAD(do_GetService), + MOZ_STORAGE_SERVICE_CONTRACTID))); + + QM_TRY_UNWRAP( + auto connection, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( + nsCOMPtr<mozIStorageConnection>, storageService, OpenDatabase, + databaseFile, mozIStorageService::CONNECTION_DEFAULT))); + + ResultConnection result(connection); + + return result; +} + +} // namespace + +QuotaClient::QuotaClient() { ::mozilla::ipc::AssertIsOnBackgroundThread(); } + +mozilla::dom::quota::Client::Type QuotaClient::GetType() { + return quota::Client::Type::FILESYSTEM; +} + +Result<quota::UsageInfo, nsresult> QuotaClient::InitOrigin( + quota::PersistenceType aPersistenceType, + const quota::OriginMetadata& aOriginMetadata, const AtomicBool& aCanceled) { + quota::AssertIsOnIOThread(); + + const Origin& origin = aOriginMetadata.mOrigin; + + { + QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& databaseFile, + data::GetDatabaseFile(origin).mapErr(toNSResult)); + + bool exists = false; + QM_TRY(MOZ_TO_RESULT(databaseFile->Exists(&exists))); + // If database doesn't already exist, we do not create it + if (!exists) { + return quota::UsageInfo(); + } + } + + QM_TRY_INSPECT(const ResultConnection& conn, + GetStorageConnection(origin).mapErr(toNSResult)); + + return data::FileSystemDatabaseManager::GetUsage(conn, origin) + .mapErr(toNSResult); +} + +nsresult QuotaClient::InitOriginWithoutTracking( + quota::PersistenceType /* aPersistenceType */, + const quota::OriginMetadata& /* aOriginMetadata */, + const AtomicBool& /* aCanceled */) { + quota::AssertIsOnIOThread(); + + // This is called when a storage/permanent/${origin}/fs directory exists. Even + // though this shouldn't happen with a "good" profile, we shouldn't return an + // error here, since that would cause origin initialization to fail. We just + // warn and otherwise ignore that. + UNKNOWN_FILE_WARNING( + NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME)); + + return NS_OK; +} + +Result<quota::UsageInfo, nsresult> QuotaClient::GetUsageForOrigin( + quota::PersistenceType aPersistenceType, + const quota::OriginMetadata& aOriginMetadata, + const AtomicBool& /* aCanceled */) { + quota::AssertIsOnIOThread(); + + MOZ_ASSERT(aPersistenceType == + quota::PersistenceType::PERSISTENCE_TYPE_DEFAULT); + + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + // We can't open the database at this point because the quota manager may not + // allow it. Use the cached value instead. + return quotaManager->GetUsageForClient(aPersistenceType, aOriginMetadata, + quota::Client::FILESYSTEM); +} + +void QuotaClient::OnOriginClearCompleted( + quota::PersistenceType aPersistenceType, const nsACString& aOrigin) { + quota::AssertIsOnIOThread(); +} + +void QuotaClient::ReleaseIOThreadObjects() { quota::AssertIsOnIOThread(); } + +void QuotaClient::AbortOperationsForLocks( + const DirectoryLockIdTable& aDirectoryLockIds) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + data::FileSystemDataManager::AbortOperationsForLocks(aDirectoryLockIds); +} + +void QuotaClient::AbortOperationsForProcess(ContentParentId aContentParentId) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); +} + +void QuotaClient::AbortAllOperations() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); +} + +void QuotaClient::StartIdleMaintenance() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); +} + +void QuotaClient::StopIdleMaintenance() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); +} + +void QuotaClient::InitiateShutdown() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + data::FileSystemDataManager::InitiateShutdown(); +} + +bool QuotaClient::IsShutdownCompleted() const { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + return data::FileSystemDataManager::IsShutdownCompleted(); +} + +nsCString QuotaClient::GetShutdownStatus() const { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + return "Not implemented"_ns; +} + +void QuotaClient::ForceKillActors() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + // Hopefully not needed. +} + +void QuotaClient::FinalizeShutdown() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + // Empty for now. +} + +already_AddRefed<mozilla::dom::quota::Client> CreateQuotaClient() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + RefPtr<QuotaClient> client = new fs::QuotaClient(); + return client.forget(); +} + +} // namespace mozilla::dom::fs diff --git a/dom/fs/parent/FileSystemQuotaClient.h b/dom/fs/parent/FileSystemQuotaClient.h new file mode 100644 index 0000000000..9afc04ee7b --- /dev/null +++ b/dom/fs/parent/FileSystemQuotaClient.h @@ -0,0 +1,25 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_FILESYSTEMQUOTACLIENT_H_ +#define DOM_FS_PARENT_FILESYSTEMQUOTACLIENT_H_ + +#include "mozilla/AlreadyAddRefed.h" + +namespace mozilla::dom { + +namespace quota { +class Client; +} // namespace quota + +namespace fs { + +already_AddRefed<mozilla::dom::quota::Client> CreateQuotaClient(); + +} // namespace fs +} // namespace mozilla::dom + +#endif // DOM_FS_PARENT_FILESYSTEMQUOTACLIENT_H_ diff --git a/dom/fs/parent/FileSystemStreamCallbacks.cpp b/dom/fs/parent/FileSystemStreamCallbacks.cpp new file mode 100644 index 0000000000..8986b49358 --- /dev/null +++ b/dom/fs/parent/FileSystemStreamCallbacks.cpp @@ -0,0 +1,46 @@ +/* -*- 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 "FileSystemStreamCallbacks.h" + +namespace mozilla::dom { + +bool FileSystemStreamCallbacks::HasNoRemoteQuotaObjectParents() const { + return !mRemoteQuotaObjectParents.Count(); +} + +void FileSystemStreamCallbacks::SetRemoteQuotaObjectParentCallback( + std::function<void()>&& aCallback) { + mRemoteQuotaObjectParentCallback = std::move(aCallback); +} + +NS_IMPL_ISUPPORTS(FileSystemStreamCallbacks, nsIInterfaceRequestor, + quota::RemoteQuotaObjectParentTracker) + +NS_IMETHODIMP +FileSystemStreamCallbacks::GetInterface(const nsIID& aIID, void** aResult) { + return QueryInterface(aIID, aResult); +} + +void FileSystemStreamCallbacks::RegisterRemoteQuotaObjectParent( + NotNull<quota::RemoteQuotaObjectParent*> aActor) { + MOZ_ASSERT(!mRemoteQuotaObjectParents.Contains(aActor)); + + mRemoteQuotaObjectParents.Insert(aActor); +} + +void FileSystemStreamCallbacks::UnregisterRemoteQuotaObjectParent( + NotNull<quota::RemoteQuotaObjectParent*> aActor) { + MOZ_ASSERT(mRemoteQuotaObjectParents.Contains(aActor)); + + mRemoteQuotaObjectParents.Remove(aActor); + + if (!mRemoteQuotaObjectParents.Count() && mRemoteQuotaObjectParentCallback) { + mRemoteQuotaObjectParentCallback(); + } +} + +} // namespace mozilla::dom diff --git a/dom/fs/parent/FileSystemStreamCallbacks.h b/dom/fs/parent/FileSystemStreamCallbacks.h new file mode 100644 index 0000000000..37028b9f9c --- /dev/null +++ b/dom/fs/parent/FileSystemStreamCallbacks.h @@ -0,0 +1,44 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_FILESYSTEMSTREAMCALLBACKS_H_ +#define DOM_FS_PARENT_FILESYSTEMSTREAMCALLBACKS_H_ + +#include "mozilla/dom/quota/RemoteQuotaObjectParentTracker.h" +#include "nsIInterfaceRequestor.h" +#include "nsTHashSet.h" + +namespace mozilla::dom { + +class FileSystemStreamCallbacks : public nsIInterfaceRequestor, + public quota::RemoteQuotaObjectParentTracker { + public: + FileSystemStreamCallbacks() = default; + + bool HasNoRemoteQuotaObjectParents() const; + + void SetRemoteQuotaObjectParentCallback(std::function<void()>&& aCallback); + + NS_DECL_THREADSAFE_ISUPPORTS + + NS_DECL_NSIINTERFACEREQUESTOR + + void RegisterRemoteQuotaObjectParent( + NotNull<quota::RemoteQuotaObjectParent*> aActor) override; + + void UnregisterRemoteQuotaObjectParent( + NotNull<quota::RemoteQuotaObjectParent*> aActor) override; + + private: + virtual ~FileSystemStreamCallbacks() = default; + + nsTHashSet<quota::RemoteQuotaObjectParent*> mRemoteQuotaObjectParents; + std::function<void()> mRemoteQuotaObjectParentCallback; +}; + +} // namespace mozilla::dom + +#endif // DOM_FS_PARENT_FILESYSTEMSTREAMCALLBACKS_H_ diff --git a/dom/fs/parent/FileSystemWritableFileStreamParent.cpp b/dom/fs/parent/FileSystemWritableFileStreamParent.cpp new file mode 100644 index 0000000000..d490ef4994 --- /dev/null +++ b/dom/fs/parent/FileSystemWritableFileStreamParent.cpp @@ -0,0 +1,43 @@ +/* -*- 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 "FileSystemWritableFileStreamParent.h" + +#include "FileSystemDataManager.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemManagerParent.h" + +namespace mozilla::dom { + +FileSystemWritableFileStreamParent::FileSystemWritableFileStreamParent( + RefPtr<FileSystemManagerParent> aManager, const fs::EntryId& aEntryId) + : mManager(std::move(aManager)), mEntryId(aEntryId) {} + +FileSystemWritableFileStreamParent::~FileSystemWritableFileStreamParent() { + MOZ_ASSERT(mClosed); +} + +mozilla::ipc::IPCResult FileSystemWritableFileStreamParent::RecvClose() { + Close(); + + return IPC_OK(); +} + +void FileSystemWritableFileStreamParent::ActorDestroy(ActorDestroyReason aWhy) { + if (!IsClosed()) { + Close(); + } +} + +void FileSystemWritableFileStreamParent::Close() { + LOG(("Closing WritableFileStream")); + + mClosed.Flip(); + + mManager->DataManagerStrongRef()->UnlockShared(mEntryId); +} + +} // namespace mozilla::dom diff --git a/dom/fs/parent/FileSystemWritableFileStreamParent.h b/dom/fs/parent/FileSystemWritableFileStreamParent.h new file mode 100644 index 0000000000..bd7db23902 --- /dev/null +++ b/dom/fs/parent/FileSystemWritableFileStreamParent.h @@ -0,0 +1,47 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_FILESYSTEMWRITABLEFILESTREAM_H_ +#define DOM_FS_PARENT_FILESYSTEMWRITABLEFILESTREAM_H_ + +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/FlippedOnce.h" +#include "mozilla/dom/PFileSystemWritableFileStreamParent.h" + +namespace mozilla::dom { + +class FileSystemManagerParent; + +class FileSystemWritableFileStreamParent + : public PFileSystemWritableFileStreamParent { + public: + FileSystemWritableFileStreamParent(RefPtr<FileSystemManagerParent> aManager, + const fs::EntryId& aEntryId); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemWritableFileStreamParent, + override) + + mozilla::ipc::IPCResult RecvClose(); + + void ActorDestroy(ActorDestroyReason aWhy) override; + + private: + virtual ~FileSystemWritableFileStreamParent(); + + bool IsClosed() const { return mClosed; } + + void Close(); + + const RefPtr<FileSystemManagerParent> mManager; + + const fs::EntryId mEntryId; + + FlippedOnce<false> mClosed; +}; + +} // namespace mozilla::dom + +#endif // DOM_FS_PARENT_FILESYSTEMWRITABLEFILESTREAM_H_ diff --git a/dom/fs/parent/ResultConnection.h b/dom/fs/parent/ResultConnection.h new file mode 100644 index 0000000000..3606a55a6b --- /dev/null +++ b/dom/fs/parent/ResultConnection.h @@ -0,0 +1,19 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_RESULTCONNECTION_H_ +#define DOM_FS_PARENT_RESULTCONNECTION_H_ + +#include "mozIStorageConnection.h" +#include "nsCOMPtr.h" + +namespace mozilla::dom::fs { + +using ResultConnection = nsCOMPtr<mozIStorageConnection>; + +} // namespace mozilla::dom::fs + +#endif // DOM_FS_PARENT_RESULTCONNECTION_H_ diff --git a/dom/fs/parent/ResultStatement.cpp b/dom/fs/parent/ResultStatement.cpp new file mode 100644 index 0000000000..0b0e2cf4a8 --- /dev/null +++ b/dom/fs/parent/ResultStatement.cpp @@ -0,0 +1,23 @@ +/* -*- 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 "ResultStatement.h" + +#include "mozIStorageConnection.h" + +namespace mozilla::dom::fs { + +Result<ResultStatement, QMResult> ResultStatement::Create( + const ResultConnection& aConnection, const nsACString& aSQLStatement) { + nsCOMPtr<mozIStorageStatement> stmt; + + QM_TRY(QM_TO_RESULT( + aConnection->CreateStatement(aSQLStatement, getter_AddRefs(stmt)))); + + return ResultStatement(stmt); +}; + +} // namespace mozilla::dom::fs diff --git a/dom/fs/parent/ResultStatement.h b/dom/fs/parent/ResultStatement.h new file mode 100644 index 0000000000..c294826ec0 --- /dev/null +++ b/dom/fs/parent/ResultStatement.h @@ -0,0 +1,156 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_RESULTSTATEMENT_H_ +#define DOM_FS_PARENT_RESULTSTATEMENT_H_ + +#include "mozIStorageStatement.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +class mozIStorageConnection; + +namespace mozilla::dom::fs { + +using Column = uint32_t; + +using ResultConnection = nsCOMPtr<mozIStorageConnection>; + +/** + * @brief ResultStatement + * - provides error monad Result<T, E> compatible interface to the lower level + * error code-based statement implementation in order to enable remote + * debugging with error stack traces + * - converts between OPFS internal data types and the generic data types of + * the lower level implementation + * - provides a customization point for requests aimed at the lower level + * implementation allowing for example to remap errors or implement mocks + */ +class ResultStatement { + public: + using underlying_t = nsCOMPtr<mozIStorageStatement>; + + explicit ResultStatement(underlying_t aStmt) : mStmt(std::move(aStmt)) {} + + ResultStatement(const ResultStatement& aOther) + : ResultStatement(aOther.mStmt) {} + + ResultStatement(ResultStatement&& aOther) noexcept + : ResultStatement(std::move(aOther.mStmt)) {} + + ResultStatement& operator=(const ResultStatement& aOther) = default; + + ResultStatement& operator=(ResultStatement&& aOther) noexcept { + mStmt = std::move(aOther.mStmt); + return *this; + } + + static Result<ResultStatement, QMResult> Create( + const ResultConnection& aConnection, const nsACString& aSQLStatement); + + // XXX Consider moving all these "inline" methods into a separate file + // called ResultStatementInlines.h. ResultStatement.h wouldn't have to then + // include ResultExtensions.h, QuotaCommon.h and mozIStorageStatement.h + // which are quite large and should be preferable only included from cpp + // files or special headers like ResultStatementInlines.h. So in the end, + // other headers would include ResultStatement.h only and other cpp files + // would include ResultStatementInlines.h. See also IndedexDababase.h and + // IndexedDatabaseInlines.h to see how it's done. + + inline nsresult BindEntryIdByName(const nsACString& aField, + const EntryId& aValue) { + return mStmt->BindUTF8StringAsBlobByName(aField, aValue); + } + + inline nsresult BindContentTypeByName(const nsACString& aField, + const ContentType& aValue) { + return mStmt->BindStringByName(aField, aValue); + } + + inline nsresult BindNameByName(const nsACString& aField, const Name& aValue) { + return mStmt->BindStringAsBlobByName(aField, aValue); + } + + inline nsresult BindPageNumberByName(const nsACString& aField, + PageNumber aValue) { + return mStmt->BindInt32ByName(aField, aValue); + } + + inline nsresult BindUsageByName(const nsACString& aField, Usage aValue) { + return mStmt->BindInt64ByName(aField, aValue); + } + + inline nsresult BindBooleanByName(const nsACString& aField, bool aValue) { + return mStmt->BindInt32ByName(aField, aValue ? 1 : 0); + } + + inline Result<bool, QMResult> GetBooleanByColumn(Column aColumn) { + int32_t value = 0; + QM_TRY(QM_TO_RESULT(mStmt->GetInt32(aColumn, &value))); + + return 0 != value; + } + + inline Result<ContentType, QMResult> GetContentTypeByColumn(Column aColumn) { + ContentType value; + QM_TRY(QM_TO_RESULT(mStmt->GetString(aColumn, value))); + + return value; + } + + inline Result<EntryId, QMResult> GetEntryIdByColumn(Column aColumn) { + EntryId value; + QM_TRY(QM_TO_RESULT(mStmt->GetBlobAsUTF8String(aColumn, value))); + + return value; + } + + inline Result<Name, QMResult> GetNameByColumn(Column aColumn) { + Name value; + QM_TRY(QM_TO_RESULT(mStmt->GetBlobAsString(aColumn, value))); + + return value; + } + + inline Result<Usage, QMResult> GetUsageByColumn(Column aColumn) { + Usage value = 0; + QM_TRY(QM_TO_RESULT(mStmt->GetInt64(aColumn, &value))); + + return value; + } + + inline bool IsNullByColumn(Column aColumn) const { + bool value = mStmt->IsNull(aColumn); + + return value; + } + + inline nsresult Execute() { return mStmt->Execute(); } + + inline Result<bool, QMResult> ExecuteStep() { + bool hasEntries = false; + QM_TRY(QM_TO_RESULT(mStmt->ExecuteStep(&hasEntries))); + + return hasEntries; + } + + inline Result<bool, QMResult> YesOrNoQuery() { + bool hasEntries = false; + QM_TRY(QM_TO_RESULT(mStmt->ExecuteStep(&hasEntries))); + MOZ_ALWAYS_TRUE(hasEntries); + return GetBooleanByColumn(0u); + } + + private: + underlying_t mStmt; +}; + +} // namespace mozilla::dom::fs + +#endif // DOM_FS_PARENT_RESULTSTATEMENT_H_ diff --git a/dom/fs/parent/datamodel/FileSystemDataManager.cpp b/dom/fs/parent/datamodel/FileSystemDataManager.cpp new file mode 100644 index 0000000000..73b5327d77 --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemDataManager.cpp @@ -0,0 +1,529 @@ +/* -*- 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 "FileSystemDataManager.h" + +#include "FileSystemDatabaseManager.h" +#include "FileSystemDatabaseManagerVersion001.h" +#include "FileSystemFileManager.h" +#include "FileSystemHashSource.h" +#include "ResultStatement.h" +#include "SchemaVersion001.h" +#include "fs/FileSystemConstants.h" +#include "mozIStorageService.h" +#include "mozStorageCID.h" +#include "mozilla/Result.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/dom/FileSystemLog.h" +#include "mozilla/dom/FileSystemManagerParent.h" +#include "mozilla/dom/quota/ClientImpl.h" +#include "mozilla/dom/quota/DirectoryLock.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "mozilla/dom/quota/UsageInfo.h" +#include "mozilla/ipc/BackgroundParent.h" +#include "nsBaseHashtable.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsIFile.h" +#include "nsIFileURL.h" +#include "nsNetCID.h" +#include "nsProxyRelease.h" +#include "nsServiceManagerUtils.h" +#include "nsString.h" +#include "nsThreadUtils.h" + +namespace mozilla::dom::fs::data { + +namespace { + +// nsCStringHashKey with disabled memmove +class nsCStringHashKeyDM : public nsCStringHashKey { + public: + explicit nsCStringHashKeyDM(const nsCStringHashKey::KeyTypePointer aKey) + : nsCStringHashKey(aKey) {} + enum { ALLOW_MEMMOVE = false }; +}; + +// When CheckedUnsafePtr's checking is enabled, it's necessary to ensure that +// the hashtable uses the copy constructor instead of memmove for moving entries +// since memmove will break CheckedUnsafePtr in a memory-corrupting way. +using FileSystemDataManagerHashKey = + std::conditional<DiagnosticAssertEnabled::value, nsCStringHashKeyDM, + nsCStringHashKey>::type; + +// Raw (but checked when the diagnostic assert is enabled) references as we +// don't want to keep FileSystemDataManager objects alive forever. When a +// FileSystemDataManager is destroyed it calls RemoveFileSystemDataManager +// to clear itself. +using FileSystemDataManagerHashtable = + nsBaseHashtable<FileSystemDataManagerHashKey, + NotNull<CheckedUnsafePtr<FileSystemDataManager>>, + MovingNotNull<CheckedUnsafePtr<FileSystemDataManager>>>; + +// This hashtable isn't protected by any mutex but it is only ever touched on +// the PBackground thread. +StaticAutoPtr<FileSystemDataManagerHashtable> gDataManagers; + +RefPtr<FileSystemDataManager> GetFileSystemDataManager(const Origin& aOrigin) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + if (gDataManagers) { + auto maybeDataManager = gDataManagers->MaybeGet(aOrigin); + if (maybeDataManager) { + RefPtr<FileSystemDataManager> result( + std::move(*maybeDataManager).unwrapBasePtr()); + return result; + } + } + + return nullptr; +} + +void AddFileSystemDataManager( + const Origin& aOrigin, const RefPtr<FileSystemDataManager>& aDataManager) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + MOZ_ASSERT(!quota::QuotaManager::IsShuttingDown()); + + if (!gDataManagers) { + gDataManagers = new FileSystemDataManagerHashtable(); + } + + MOZ_ASSERT(!gDataManagers->Contains(aOrigin)); + gDataManagers->InsertOrUpdate(aOrigin, + WrapMovingNotNullUnchecked(aDataManager)); +} + +void RemoveFileSystemDataManager(const Origin& aOrigin) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + MOZ_ASSERT(gDataManagers); + const DebugOnly<bool> removed = gDataManagers->Remove(aOrigin); + MOZ_ASSERT(removed); + + if (!gDataManagers->Count()) { + gDataManagers = nullptr; + } +} + +Result<ResultConnection, QMResult> GetStorageConnection( + const Origin& aOrigin, const int64_t aDirectoryLockId) { + MOZ_ASSERT(aDirectoryLockId >= 0); + + // Ensure that storage is initialized and file system folder exists! + QM_TRY_INSPECT(const auto& dbFileUrl, + GetDatabaseFileURL(aOrigin, aDirectoryLockId)); + + QM_TRY_INSPECT( + const auto& storageService, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( + nsCOMPtr<mozIStorageService>, MOZ_SELECT_OVERLOAD(do_GetService), + MOZ_STORAGE_SERVICE_CONTRACTID))); + + QM_TRY_UNWRAP(auto connection, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( + nsCOMPtr<mozIStorageConnection>, storageService, + OpenDatabaseWithFileURL, dbFileUrl, ""_ns, + mozIStorageService::CONNECTION_DEFAULT))); + + ResultConnection result(connection); + + return result; +} + +} // namespace + +Result<EntryId, QMResult> GetRootHandle(const Origin& origin) { + MOZ_ASSERT(!origin.IsEmpty()); + + return FileSystemHashSource::GenerateHash(origin, kRootName); +} + +Result<EntryId, QMResult> GetEntryHandle( + const FileSystemChildMetadata& aHandle) { + MOZ_ASSERT(!aHandle.parentId().IsEmpty()); + + return FileSystemHashSource::GenerateHash(aHandle.parentId(), + aHandle.childName()); +} + +FileSystemDataManager::FileSystemDataManager( + const quota::OriginMetadata& aOriginMetadata, + MovingNotNull<nsCOMPtr<nsIEventTarget>> aIOTarget, + MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue) + : mOriginMetadata(aOriginMetadata), + mBackgroundTarget(WrapNotNull(GetCurrentSerialEventTarget())), + mIOTarget(std::move(aIOTarget)), + mIOTaskQueue(std::move(aIOTaskQueue)), + mRegCount(0), + mState(State::Initial) {} + +FileSystemDataManager::~FileSystemDataManager() { + MOZ_ASSERT(mState == State::Closed); + MOZ_ASSERT(!mDatabaseManager); +} + +RefPtr<FileSystemDataManager::CreatePromise> +FileSystemDataManager::GetOrCreateFileSystemDataManager( + const quota::OriginMetadata& aOriginMetadata) { + if (quota::QuotaManager::IsShuttingDown()) { + return CreatePromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + if (RefPtr<FileSystemDataManager> dataManager = + GetFileSystemDataManager(aOriginMetadata.mOrigin)) { + if (dataManager->IsOpening()) { + // We have to wait for the open to be finished before resolving the + // promise. The manager can't close itself in the meantime because we + // add a new registration in the lambda capture list. + return dataManager->OnOpen()->Then( + GetCurrentSerialEventTarget(), __func__, + [dataManager = Registered<FileSystemDataManager>(dataManager)]( + const BoolPromise::ResolveOrRejectValue&) { + return CreatePromise::CreateAndResolve(dataManager, __func__); + }); + } + + if (dataManager->IsClosing()) { + // First, we need to wait for the close to be finished. After that the + // manager is closed and it can't be opened again. The only option is + // to create a new manager and open it. However, all this stuff is + // asynchronous, so it can happen that something else called + // `GetOrCreateFileSystemManager` in the meantime. For that reason, we + // shouldn't try to create a new manager and open it here, a "recursive" + // call to `GetOrCreateFileSystemManager` will handle any new situation. + return dataManager->OnClose()->Then( + GetCurrentSerialEventTarget(), __func__, + [aOriginMetadata](const BoolPromise::ResolveOrRejectValue&) { + return GetOrCreateFileSystemDataManager(aOriginMetadata); + }); + } + + return CreatePromise::CreateAndResolve( + Registered<FileSystemDataManager>(std::move(dataManager)), __func__); + } + + QM_TRY_UNWRAP(auto streamTransportService, + MOZ_TO_RESULT_GET_TYPED(nsCOMPtr<nsIEventTarget>, + MOZ_SELECT_OVERLOAD(do_GetService), + NS_STREAMTRANSPORTSERVICE_CONTRACTID), + CreatePromise::CreateAndReject(NS_ERROR_FAILURE, __func__)); + + nsCString taskQueueName("OPFS "_ns + aOriginMetadata.mOrigin); + + RefPtr<TaskQueue> ioTaskQueue = + TaskQueue::Create(do_AddRef(streamTransportService), taskQueueName.get()); + + auto dataManager = MakeRefPtr<FileSystemDataManager>( + aOriginMetadata, WrapMovingNotNull(streamTransportService), + WrapMovingNotNull(ioTaskQueue)); + + AddFileSystemDataManager(aOriginMetadata.mOrigin, dataManager); + + return dataManager->BeginOpen()->Then( + GetCurrentSerialEventTarget(), __func__, + [dataManager = Registered<FileSystemDataManager>(dataManager)]( + const BoolPromise::ResolveOrRejectValue&) { + return CreatePromise::CreateAndResolve(dataManager, __func__); + }); +} + +// static +void FileSystemDataManager::AbortOperationsForLocks( + const quota::Client::DirectoryLockIdTable& aDirectoryLockIds) { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + // XXX Share the iteration code with `InitiateShutdown`, for example by + // creating a helper function which would take a predicate function. + + if (!gDataManagers) { + return; + } + + for (const auto& dataManager : gDataManagers->Values()) { + // Check if the Manager holds an acquired DirectoryLock. Origin clearing + // can't be blocked by this Manager if there is no acquired DirectoryLock. + // If there is an acquired DirectoryLock, check if the table contains the + // lock for the Manager. + if (quota::Client::IsLockForObjectAcquiredAndContainedInLockTable( + *dataManager, aDirectoryLockIds)) { + dataManager->RequestAllowToClose(); + } + } +} + +// static +void FileSystemDataManager::InitiateShutdown() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + if (!gDataManagers) { + return; + } + + for (const auto& dataManager : gDataManagers->Values()) { + dataManager->RequestAllowToClose(); + } +} + +// static +bool FileSystemDataManager::IsShutdownCompleted() { + ::mozilla::ipc::AssertIsOnBackgroundThread(); + + return !gDataManagers; +} + +void FileSystemDataManager::AssertIsOnIOTarget() const { + DebugOnly<bool> current = false; + MOZ_ASSERT(NS_SUCCEEDED(mIOTarget->IsOnCurrentThread(¤t))); + MOZ_ASSERT(current); +} + +void FileSystemDataManager::Register() { mRegCount++; } + +void FileSystemDataManager::Unregister() { + MOZ_ASSERT(mRegCount > 0); + + mRegCount--; + + if (IsInactive()) { + BeginClose(); + } +} + +void FileSystemDataManager::RegisterActor( + NotNull<FileSystemManagerParent*> aActor) { + MOZ_ASSERT(!mBackgroundThreadAccessible.Access()->mActors.Contains(aActor)); + + mBackgroundThreadAccessible.Access()->mActors.Insert(aActor); +} + +void FileSystemDataManager::UnregisterActor( + NotNull<FileSystemManagerParent*> aActor) { + MOZ_ASSERT(mBackgroundThreadAccessible.Access()->mActors.Contains(aActor)); + + mBackgroundThreadAccessible.Access()->mActors.Remove(aActor); + + if (IsInactive()) { + BeginClose(); + } +} + +RefPtr<BoolPromise> FileSystemDataManager::OnOpen() { + MOZ_ASSERT(mState == State::Opening); + + return mOpenPromiseHolder.Ensure(__func__); +} + +RefPtr<BoolPromise> FileSystemDataManager::OnClose() { + MOZ_ASSERT(mState == State::Closing); + + return mClosePromiseHolder.Ensure(__func__); +} + +bool FileSystemDataManager::IsLocked(const EntryId& aEntryId) const { + return mExclusiveLocks.Contains(aEntryId); +} + +bool FileSystemDataManager::LockExclusive(const EntryId& aEntryId) { + if (IsLocked(aEntryId)) { + return false; + } + + LOG_VERBOSE(("ExclusiveLock")); + mExclusiveLocks.Insert(aEntryId); + + return true; +} + +void FileSystemDataManager::UnlockExclusive(const EntryId& aEntryId) { + MOZ_ASSERT(mExclusiveLocks.Contains(aEntryId)); + + LOG_VERBOSE(("ExclusiveUnlock")); + mExclusiveLocks.Remove(aEntryId); + + QM_WARNONLY_TRY(MOZ_TO_RESULT(mDatabaseManager->UpdateUsage(aEntryId))); +} + +bool FileSystemDataManager::LockShared(const EntryId& aEntryId) { + return LockExclusive(aEntryId); +} + +void FileSystemDataManager::UnlockShared(const EntryId& aEntryId) { + UnlockExclusive(aEntryId); +} + +bool FileSystemDataManager::IsInactive() const { + return !mRegCount && !mBackgroundThreadAccessible.Access()->mActors.Count(); +} + +void FileSystemDataManager::RequestAllowToClose() { + for (const auto& actor : mBackgroundThreadAccessible.Access()->mActors) { + actor->RequestAllowToClose(); + } +} + +RefPtr<BoolPromise> FileSystemDataManager::BeginOpen() { + MOZ_ASSERT(mState == State::Initial); + + mState = State::Opening; + + QM_TRY_UNWRAP(const NotNull<RefPtr<quota::QuotaManager>> quotaManager, + quota::QuotaManager::GetOrCreate(), CreateAndRejectBoolPromise); + + RefPtr<quota::ClientDirectoryLock> directoryLock = + quotaManager->CreateDirectoryLock(quota::PERSISTENCE_TYPE_DEFAULT, + mOriginMetadata, + mozilla::dom::quota::Client::FILESYSTEM, + /* aExclusive */ false); + + directoryLock->Acquire() + ->Then(GetCurrentSerialEventTarget(), __func__, + [self = RefPtr<FileSystemDataManager>(this), + directoryLock = directoryLock]( + const BoolPromise::ResolveOrRejectValue& value) mutable { + if (value.IsReject()) { + return BoolPromise::CreateAndReject(value.RejectValue(), + __func__); + } + + self->mDirectoryLock = std::move(directoryLock); + + return BoolPromise::CreateAndResolve(true, __func__); + }) + ->Then(quotaManager->IOThread(), __func__, + [self = RefPtr<FileSystemDataManager>(this)]( + const BoolPromise::ResolveOrRejectValue& value) mutable { + auto autoProxyReleaseManager = MakeScopeExit([&self] { + nsCOMPtr<nsISerialEventTarget> target = + self->MutableBackgroundTargetPtr(); + + NS_ProxyRelease("ReleaseFileSystemDataManager", target, + self.forget()); + }); + + if (value.IsReject()) { + return BoolPromise::CreateAndReject(value.RejectValue(), + __func__); + } + + QM_TRY(MOZ_TO_RESULT( + EnsureFileSystemDirectory(self->mOriginMetadata)), + CreateAndRejectBoolPromise); + + return BoolPromise::CreateAndResolve(true, __func__); + }) + ->Then(MutableIOTargetPtr(), __func__, + [self = RefPtr<FileSystemDataManager>(this)]( + const BoolPromise::ResolveOrRejectValue& value) mutable { + auto autoProxyReleaseManager = MakeScopeExit([&self] { + nsCOMPtr<nsISerialEventTarget> target = + self->MutableBackgroundTargetPtr(); + + NS_ProxyRelease("ReleaseFileSystemDataManager", target, + self.forget()); + }); + + if (value.IsReject()) { + return BoolPromise::CreateAndReject(value.RejectValue(), + __func__); + } + + QM_TRY_UNWRAP( + auto connection, + fs::data::GetStorageConnection(self->mOriginMetadata.mOrigin, + self->mDirectoryLock->Id()), + CreateAndRejectBoolPromiseFromQMResult); + + QM_TRY_UNWRAP(DatabaseVersion version, + SchemaVersion001::InitializeConnection( + connection, self->mOriginMetadata.mOrigin), + CreateAndRejectBoolPromiseFromQMResult); + + if (1 == version) { + QM_TRY_UNWRAP( + FileSystemFileManager fmRes, + FileSystemFileManager::CreateFileSystemFileManager( + self->mOriginMetadata.mOrigin), + CreateAndRejectBoolPromiseFromQMResult); + + QM_TRY_UNWRAP( + EntryId rootId, + fs::data::GetRootHandle(self->mOriginMetadata.mOrigin), + CreateAndRejectBoolPromiseFromQMResult); + + self->mDatabaseManager = + MakeUnique<FileSystemDatabaseManagerVersion001>( + self, std::move(connection), + MakeUnique<FileSystemFileManager>(std::move(fmRes)), + rootId); + } + + return BoolPromise::CreateAndResolve(true, __func__); + }) + ->Then(GetCurrentSerialEventTarget(), __func__, + [self = RefPtr<FileSystemDataManager>(this)]( + const BoolPromise::ResolveOrRejectValue& value) { + if (value.IsReject()) { + self->mState = State::Initial; + + self->mOpenPromiseHolder.RejectIfExists(value.RejectValue(), + __func__); + + } else { + self->mState = State::Open; + + self->mOpenPromiseHolder.ResolveIfExists(true, __func__); + } + }); + + return OnOpen(); +} + +RefPtr<BoolPromise> FileSystemDataManager::BeginClose() { + MOZ_ASSERT(mState != State::Closing && mState != State::Closed); + MOZ_ASSERT(IsInactive()); + + mState = State::Closing; + + InvokeAsync(MutableIOTargetPtr(), __func__, + [self = RefPtr<FileSystemDataManager>(this)]() mutable { + auto autoProxyReleaseManager = MakeScopeExit([&self] { + nsCOMPtr<nsISerialEventTarget> target = + self->MutableBackgroundTargetPtr(); + + NS_ProxyRelease("ReleaseFileSystemDataManager", target, + self.forget()); + }); + + if (self->mDatabaseManager) { + self->mDatabaseManager->Close(); + self->mDatabaseManager = nullptr; + } + + return BoolPromise::CreateAndResolve(true, __func__); + }) + ->Then(MutableBackgroundTargetPtr(), __func__, + [self = RefPtr<FileSystemDataManager>(this)]( + const BoolPromise::ResolveOrRejectValue&) { + return self->mIOTaskQueue->BeginShutdown(); + }) + ->Then(MutableBackgroundTargetPtr(), __func__, + [self = RefPtr<FileSystemDataManager>(this)]( + const ShutdownPromise::ResolveOrRejectValue&) { + self->mDirectoryLock = nullptr; + + RemoveFileSystemDataManager(self->mOriginMetadata.mOrigin); + + self->mState = State::Closed; + + self->mClosePromiseHolder.ResolveIfExists(true, __func__); + }); + + return OnClose(); +} + +} // namespace mozilla::dom::fs::data diff --git a/dom/fs/parent/datamodel/FileSystemDataManager.h b/dom/fs/parent/datamodel/FileSystemDataManager.h new file mode 100644 index 0000000000..12ebbd2b41 --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemDataManager.h @@ -0,0 +1,161 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATAMANAGER_H_ +#define DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATAMANAGER_H_ + +#include "ResultConnection.h" +#include "mozilla/NotNull.h" +#include "mozilla/TaskQueue.h" +#include "mozilla/ThreadBound.h" +#include "mozilla/dom/FileSystemHelpers.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/quota/CheckedUnsafePtr.h" +#include "mozilla/dom/quota/CommonMetadata.h" +#include "mozilla/dom/quota/ForwardDecls.h" +#include "nsCOMPtr.h" +#include "nsISupportsUtils.h" +#include "nsString.h" +#include "nsTHashSet.h" + +namespace mozilla { + +template <typename V, typename E> +class Result; + +namespace dom { + +class FileSystemManagerParent; + +namespace fs { +class FileSystemChildMetadata; +} // namespace fs + +namespace quota { +class DirectoryLock; +} // namespace quota + +namespace fs::data { + +class FileSystemDatabaseManager; + +Result<EntryId, QMResult> GetRootHandle(const Origin& origin); + +Result<EntryId, QMResult> GetEntryHandle( + const FileSystemChildMetadata& aHandle); + +class FileSystemDataManager + : public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> { + public: + enum struct State : uint8_t { Initial = 0, Opening, Open, Closing, Closed }; + + FileSystemDataManager(const quota::OriginMetadata& aOriginMetadata, + MovingNotNull<nsCOMPtr<nsIEventTarget>> aIOTarget, + MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue); + + // IsExclusive is true because we want to allow the move operations. There's + // always just one consumer anyway. + using CreatePromise = MozPromise<Registered<FileSystemDataManager>, nsresult, + /* IsExclusive */ true>; + static RefPtr<CreatePromise> GetOrCreateFileSystemDataManager( + const quota::OriginMetadata& aOriginMetadata); + + static void AbortOperationsForLocks( + const quota::Client::DirectoryLockIdTable& aDirectoryLockIds); + + static void InitiateShutdown(); + + static bool IsShutdownCompleted(); + + NS_INLINE_DECL_REFCOUNTING(FileSystemDataManager) + + void AssertIsOnIOTarget() const; + + const quota::OriginMetadata& OriginMetadataRef() const { + return mOriginMetadata; + } + + nsISerialEventTarget* MutableBackgroundTargetPtr() const { + return mBackgroundTarget.get(); + } + + nsISerialEventTarget* MutableIOTargetPtr() const { + return mIOTaskQueue.get(); + } + + Maybe<quota::DirectoryLock&> MaybeDirectoryLockRef() const { + return ToMaybeRef(mDirectoryLock.get()); + } + + FileSystemDatabaseManager* MutableDatabaseManagerPtr() const { + MOZ_ASSERT(mDatabaseManager); + + return mDatabaseManager.get(); + } + + void Register(); + + void Unregister(); + + void RegisterActor(NotNull<FileSystemManagerParent*> aActor); + + void UnregisterActor(NotNull<FileSystemManagerParent*> aActor); + + bool IsOpen() const { return mState == State::Open; } + + RefPtr<BoolPromise> OnOpen(); + + RefPtr<BoolPromise> OnClose(); + + bool IsLocked(const EntryId& aEntryId) const; + + bool LockExclusive(const EntryId& aEntryId); + + void UnlockExclusive(const EntryId& aEntryId); + + bool LockShared(const EntryId& aEntryId); + + void UnlockShared(const EntryId& aEntryId); + + protected: + virtual ~FileSystemDataManager(); + + bool IsInactive() const; + + bool IsOpening() const { return mState == State::Opening; } + + bool IsClosing() const { return mState == State::Closing; } + + void RequestAllowToClose(); + + RefPtr<BoolPromise> BeginOpen(); + + RefPtr<BoolPromise> BeginClose(); + + // Things touched on background thread only. + struct BackgroundThreadAccessible { + nsTHashSet<FileSystemManagerParent*> mActors; + }; + ThreadBound<BackgroundThreadAccessible> mBackgroundThreadAccessible; + + const quota::OriginMetadata mOriginMetadata; + nsTHashSet<EntryId> mExclusiveLocks; + const NotNull<nsCOMPtr<nsISerialEventTarget>> mBackgroundTarget; + const NotNull<nsCOMPtr<nsIEventTarget>> mIOTarget; + const NotNull<RefPtr<TaskQueue>> mIOTaskQueue; + RefPtr<quota::DirectoryLock> mDirectoryLock; + UniquePtr<FileSystemDatabaseManager> mDatabaseManager; + MozPromiseHolder<BoolPromise> mOpenPromiseHolder; + MozPromiseHolder<BoolPromise> mClosePromiseHolder; + uint32_t mRegCount; + State mState; +}; + +} // namespace fs::data +} // namespace dom +} // namespace mozilla + +#endif // DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATAMANAGER_H_ diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManager.cpp b/dom/fs/parent/datamodel/FileSystemDatabaseManager.cpp new file mode 100644 index 0000000000..7fc37da8d5 --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemDatabaseManager.cpp @@ -0,0 +1,58 @@ +/* -*- 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 "FileSystemDatabaseManager.h" + +#include "FileSystemDatabaseManagerVersion001.h" +#include "FileSystemFileManager.h" +#include "ResultConnection.h" +#include "ResultStatement.h" +#include "SchemaVersion001.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" + +namespace mozilla::dom::fs::data { + +/* static */ +Result<quota::UsageInfo, QMResult> FileSystemDatabaseManager::GetUsage( + const ResultConnection& aConnection, const Origin& aOrigin) { + QM_TRY_INSPECT(const auto& databaseFile, GetDatabaseFile(aOrigin)); + + // If database is deleted between connection creation and now, error + int64_t dbSize = 0; + QM_TRY(QM_TO_RESULT(databaseFile->GetFileSize(&dbSize))); + + quota::UsageInfo result(quota::DatabaseUsageType(Some(dbSize))); + + DatabaseVersion version = 0; + QM_TRY(QM_TO_RESULT(aConnection->GetSchemaVersion(&version))); + + switch (version) { + case 0: { + return result; + } + + case 1: { + QM_TRY_INSPECT( + const Usage& fileUsage, + FileSystemDatabaseManagerVersion001::GetFileUsage(aConnection)); + + // XXX: DatabaseUsage is currently total usage for most forms of storage + result += quota::DatabaseUsageType(Some(fileUsage)); + + return result; + } + + default: + break; + } + + return Err(QMResult(NS_ERROR_NOT_IMPLEMENTED)); +} + +} // namespace mozilla::dom::fs::data diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManager.h b/dom/fs/parent/datamodel/FileSystemDatabaseManager.h new file mode 100644 index 0000000000..259cd4d392 --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemDatabaseManager.h @@ -0,0 +1,151 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGER_H_ +#define DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGER_H_ + +#include "ResultConnection.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/quota/ForwardDecls.h" +#include "mozilla/dom/quota/UsageInfo.h" +#include "nsStringFwd.h" + +template <class T> +class nsCOMPtr; + +class nsIFile; + +namespace mozilla { + +template <typename V, typename E> +class Result; + +namespace dom::fs { + +class FileSystemChildMetadata; +class FileSystemEntryMetadata; +class FileSystemDirectoryListing; +class FileSystemEntryPair; + +namespace data { + +class FileSystemDatabaseManager { + public: + /** + * @brief Obtains the current total usage for origin and connection. + * + * @return Result<quota::UsageInfo, QMResult> On success, + * - field UsageInfo::DatabaseUsage contains the sum of current + * total database and file usage, + * - field UsageInfo::FileUsage is not used and should be equal to Nothing. + * + * If the disk is inaccessible, various IO related errors may be returned. + */ + static Result<quota::UsageInfo, QMResult> GetUsage( + const ResultConnection& aConnection, const Origin& aOrigin); + + /** + * @brief Refreshes the stored file size. + * + * @param aEntry EntryId of the file whose size is refreshed. + */ + virtual nsresult UpdateUsage(const EntryId& aEntry) = 0; + + /** + * @brief Returns directory identifier, optionally creating it if it doesn't + * exist + * + * @param aHandle Current directory and filename + * @return Result<bool, QMResult> Directory identifier or error + */ + virtual Result<EntryId, QMResult> GetOrCreateDirectory( + const FileSystemChildMetadata& aHandle, bool aCreate) = 0; + + /** + * @brief Returns file identifier, optionally creating it if it doesn't exist + * + * @param aHandle Current directory and filename + * @return Result<bool, QMResult> File identifier or error + */ + virtual Result<EntryId, QMResult> GetOrCreateFile( + const FileSystemChildMetadata& aHandle, bool aCreate) = 0; + + /** + * @brief Returns the properties of a file corresponding to a file handle + */ + virtual nsresult GetFile(const EntryId& aEntryId, nsString& aType, + TimeStamp& lastModifiedMilliSeconds, Path& aPath, + nsCOMPtr<nsIFile>& aFile) const = 0; + + virtual Result<FileSystemDirectoryListing, QMResult> GetDirectoryEntries( + const EntryId& aParent, PageNumber aPage) const = 0; + + /** + * @brief Removes a directory + * + * @param aHandle Current directory and filename + * @return Result<bool, QMResult> False if file did not exist, otherwise true + * or error + */ + virtual Result<bool, QMResult> RemoveDirectory( + const FileSystemChildMetadata& aHandle, bool aRecursive) = 0; + + /** + * @brief Removes a file + * + * @param aHandle Current directory and filename + * @return Result<bool, QMResult> False if file did not exist, otherwise true + * or error + */ + virtual Result<bool, QMResult> RemoveFile( + const FileSystemChildMetadata& aHandle) = 0; + + /** + * @brief Rename a file/directory + * + * @param aHandle Source directory or file + * @param aNewName New entry name + * @return Result<bool, QMResult> False if entry didn't exist, otherwise true + * or error + */ + virtual Result<bool, QMResult> RenameEntry( + const FileSystemEntryMetadata& aHandle, const Name& aNewName) = 0; + + /** + * @brief Move a file/directory + * + * @param aHandle Source directory or file + * @param aNewDesignation Destination directory and entry name + * @return Result<bool, QMResult> False if entry didn't exist, otherwise true + * or error + */ + virtual Result<bool, QMResult> MoveEntry( + const FileSystemEntryMetadata& aHandle, + const FileSystemChildMetadata& aNewDesignation) = 0; + + /** + * @brief Tries to connect a parent directory to a file system item with a + * path, excluding the parent directory + * + * @param aHandle Pair of parent directory and child item candidates + * @return Result<Path, QMResult> Path or error if no it didn't exists + */ + virtual Result<Path, QMResult> Resolve( + const FileSystemEntryPair& aEndpoints) const = 0; + + /** + * @brief Close database connection. + */ + virtual void Close() = 0; + + virtual ~FileSystemDatabaseManager() = default; +}; + +} // namespace data +} // namespace dom::fs +} // namespace mozilla + +#endif // DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGER_H_ diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.cpp b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.cpp new file mode 100644 index 0000000000..87105a668b --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.cpp @@ -0,0 +1,1002 @@ +/* -*- 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 "FileSystemDatabaseManagerVersion001.h" + +#include "FileSystemFileManager.h" +#include "ResultStatement.h" +#include "mozStorageHelper.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/QuotaCommon.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/dom/quota/ResultExtensions.h" + +namespace mozilla::dom { + +using FileSystemEntries = nsTArray<fs::FileSystemEntryMetadata>; + +namespace fs::data { + +namespace { + +Result<bool, QMResult> ApplyEntryExistsQuery( + const FileSystemConnection& aConnection, const nsACString& aQuery, + const FileSystemChildMetadata& aHandle) { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, aQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId()))); + QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName()))); + + return stmt.YesOrNoQuery(); +} + +Result<bool, QMResult> ApplyEntryExistsQuery( + const FileSystemConnection& aConnection, const nsACString& aQuery, + const EntryId& aEntry) { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, aQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntry))); + + return stmt.YesOrNoQuery(); +} + +Result<bool, QMResult> IsDirectoryEmpty(FileSystemConnection& mConnection, + const EntryId& aEntryId) { + const nsLiteralCString isDirEmptyQuery = + "SELECT EXISTS (" + "SELECT 1 FROM Entries WHERE parent = :parent " + ");"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, isDirEmptyQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aEntryId))); + QM_TRY_UNWRAP(bool childrenExist, stmt.YesOrNoQuery()); + + return !childrenExist; +} + +Result<bool, QMResult> DoesDirectoryExist( + const FileSystemConnection& mConnection, + const FileSystemChildMetadata& aHandle) { + MOZ_ASSERT(!aHandle.parentId().IsEmpty()); + + const nsCString existsQuery = + "SELECT EXISTS " + "(SELECT 1 FROM Directories JOIN Entries USING (handle) " + "WHERE Directories.name = :name AND Entries.parent = :parent ) " + ";"_ns; + + QM_TRY_RETURN(ApplyEntryExistsQuery(mConnection, existsQuery, aHandle)); +} + +Result<bool, QMResult> DoesDirectoryExist( + const FileSystemConnection& mConnection, const EntryId& aEntry) { + MOZ_ASSERT(!aEntry.IsEmpty()); + + const nsCString existsQuery = + "SELECT EXISTS " + "(SELECT 1 FROM Directories WHERE handle = :handle ) " + ";"_ns; + + QM_TRY_RETURN(ApplyEntryExistsQuery(mConnection, existsQuery, aEntry)); +} + +Result<Path, QMResult> ResolveReversedPath( + const FileSystemConnection& aConnection, + const FileSystemEntryPair& aEndpoints) { + const nsCString pathQuery = + "WITH RECURSIVE followPath(handle, parent) AS ( " + "SELECT handle, parent " + "FROM Entries " + "WHERE handle=:entryId " + "UNION " + "SELECT Entries.handle, Entries.parent FROM followPath, Entries " + "WHERE followPath.parent=Entries.handle ) " + "SELECT COALESCE(Directories.name, Files.name), handle " + "FROM followPath " + "LEFT JOIN Directories USING(handle) " + "LEFT JOIN Files USING(handle);"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, pathQuery)); + QM_TRY( + QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEndpoints.childId()))); + QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); + + Path pathResult; + while (moreResults) { + QM_TRY_UNWRAP(Name entryName, stmt.GetNameByColumn(/* Column */ 0u)); + QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 1u)); + + if (aEndpoints.parentId() == entryId) { + return pathResult; + } + pathResult.AppendElement(entryName); + + QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep()); + } + + // Spec wants us to return 'null' for not-an-ancestor case + pathResult.Clear(); + return pathResult; +} + +Result<bool, QMResult> IsAncestor(const FileSystemConnection& aConnection, + const FileSystemEntryPair& aEndpoints) { + const nsCString pathQuery = + "WITH RECURSIVE followPath(handle, parent) AS ( " + "SELECT handle, parent " + "FROM Entries " + "WHERE handle=:entryId " + "UNION " + "SELECT Entries.handle, Entries.parent FROM followPath, Entries " + "WHERE followPath.parent=Entries.handle ) " + "SELECT EXISTS " + "(SELECT 1 FROM followPath " + "WHERE handle=:possibleAncestor ) " + ";"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, pathQuery)); + QM_TRY( + QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEndpoints.childId()))); + QM_TRY(QM_TO_RESULT( + stmt.BindEntryIdByName("possibleAncestor"_ns, aEndpoints.parentId()))); + + return stmt.YesOrNoQuery(); +} + +Result<bool, QMResult> DoesFileExist(const FileSystemConnection& aConnection, + const FileSystemChildMetadata& aHandle) { + MOZ_ASSERT(!aHandle.parentId().IsEmpty()); + + const nsCString existsQuery = + "SELECT EXISTS " + "(SELECT 1 FROM Files JOIN Entries USING (handle) " + "WHERE Files.name = :name AND Entries.parent = :parent ) " + ";"_ns; + + QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aHandle)); +} + +Result<bool, QMResult> DoesFileExist(const FileSystemConnection& aConnection, + const EntryId& aEntry) { + MOZ_ASSERT(!aEntry.IsEmpty()); + + const nsCString existsQuery = + "SELECT EXISTS " + "(SELECT 1 FROM Files WHERE handle = :handle ) " + ";"_ns; + + QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aEntry)); +} + +Result<EntryId, QMResult> FindParent(const FileSystemConnection& aConnection, + const EntryId& aEntryId) { + const nsCString aParentQuery = + "SELECT handle FROM Entries " + "WHERE handle IN ( " + "SELECT parent FROM Entries WHERE " + "handle = :entryId ) " + ";"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, aParentQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); + QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); + + if (!moreResults) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + QM_TRY_UNWRAP(EntryId parentId, stmt.GetEntryIdByColumn(/* Column */ 0u)); + return parentId; +} + +nsresult GetFileAttributes(const FileSystemConnection& aConnection, + const EntryId& aEntryId, ContentType& aType) { + const nsLiteralCString getFileLocation = + "SELECT type FROM Files INNER JOIN Entries USING(handle) " + "WHERE handle = :entryId " + ";"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, getFileLocation)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId))); + QM_TRY_UNWRAP(bool hasEntries, stmt.ExecuteStep()); + + // Type is an optional attribute + if (!hasEntries || stmt.IsNullByColumn(/* Column */ 0u)) { + return NS_OK; + } + + QM_TRY_UNWRAP(aType, stmt.GetContentTypeByColumn(/* Column */ 0u)); + + return NS_OK; +} + +nsresult GetEntries(const FileSystemConnection& aConnection, + const nsACString& aUnboundStmt, const EntryId& aParent, + PageNumber aPage, bool aDirectory, + FileSystemEntries& aEntries) { + // The entries inside a directory are sent to the child process in batches + // of pageSize items. Large value ensures that iteration is less often delayed + // by IPC messaging and querying the database. + // TODO: The current value 1024 is not optimized. + // TODO: Value "pageSize" is shared with the iterator implementation and + // should be defined in a common place. + const int32_t pageSize = 1024; + + QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(aConnection, aParent)); + if (!exists) { + return NS_ERROR_DOM_NOT_FOUND_ERR; + } + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, aUnboundStmt)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aParent))); + QM_TRY(QM_TO_RESULT(stmt.BindPageNumberByName("pageSize"_ns, pageSize))); + QM_TRY(QM_TO_RESULT( + stmt.BindPageNumberByName("pageOffset"_ns, aPage * pageSize))); + + QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); + + while (moreResults) { + QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u)); + QM_TRY_UNWRAP(Name entryName, stmt.GetNameByColumn(/* Column */ 1u)); + + FileSystemEntryMetadata metadata(entryId, entryName, aDirectory); + aEntries.AppendElement(metadata); + + QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep()); + } + + return NS_OK; +} + +Result<EntryId, QMResult> GetUniqueEntryId( + const FileSystemConnection& aConnection, + const FileSystemChildMetadata& aHandle) { + const nsCString existsQuery = + "SELECT EXISTS " + "(SELECT 1 FROM Entries " + "WHERE handle = :handle )" + ";"_ns; + + FileSystemChildMetadata generatorInput = aHandle; + + const size_t maxRounds = 1024u; + + for (size_t hangGuard = 0u; hangGuard < maxRounds; ++hangGuard) { + QM_TRY_UNWRAP(EntryId entryId, fs::data::GetEntryHandle(generatorInput)); + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, existsQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); + + QM_TRY_UNWRAP(bool alreadyInUse, stmt.YesOrNoQuery()); + + if (!alreadyInUse) { + return entryId; + } + + generatorInput.parentId() = entryId; + } + + return Err(QMResult(NS_ERROR_UNEXPECTED)); +} + +Result<EntryId, QMResult> FindEntryId(const FileSystemConnection& aConnection, + const FileSystemChildMetadata& aHandle, + bool aIsFile) { + const nsCString aDirectoryQuery = + "SELECT Entries.handle FROM Directories JOIN Entries USING (handle) " + "WHERE Directories.name = :name AND Entries.parent = :parent " + ";"_ns; + + const nsCString aFileQuery = + "SELECT Entries.handle FROM Files JOIN Entries USING (handle) " + "WHERE Files.name = :name AND Entries.parent = :parent " + ";"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create( + aConnection, aIsFile ? aFileQuery : aDirectoryQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId()))); + QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, aHandle.childName()))); + QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); + + if (!moreResults) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u)); + + return entryId; +} + +bool IsSame(const FileSystemConnection& aConnection, + const FileSystemEntryMetadata& aHandle, + const FileSystemChildMetadata& aNewHandle, bool aIsFile) { + MOZ_ASSERT(!aNewHandle.parentId().IsEmpty()); + + QM_TRY_UNWRAP(EntryId entryId, FindEntryId(aConnection, aNewHandle, aIsFile), + false); + return entryId == aHandle.entryId(); +} + +Result<bool, QMResult> IsFile(const FileSystemConnection& aConnection, + const EntryId& aEntryId) { + QM_TRY_UNWRAP(bool exists, DoesFileExist(aConnection, aEntryId)); + if (exists) { + return true; + } + + QM_TRY_UNWRAP(exists, DoesDirectoryExist(aConnection, aEntryId)); + if (exists) { + return false; + } + + // Doesn't exist + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); +} + +nsresult PerformRename(const FileSystemConnection& aConnection, + const FileSystemEntryMetadata& aHandle, + const Name& aNewName, + const nsLiteralCString& aNameUpdateQuery) { + MOZ_ASSERT(!aHandle.entryId().IsEmpty()); + MOZ_ASSERT(IsValidName(aHandle.entryName())); + + // same-name is checked in RenameEntry() + if (!IsValidName(aNewName)) { + 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, + ResultStatement::Create(aConnection, aNameUpdateQuery) + .mapErr(toNSResult)); + QM_TRY(MOZ_TO_RESULT(stmt.BindNameByName("name"_ns, aNewName))); + QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aHandle.entryId()))); + QM_TRY(MOZ_TO_RESULT(stmt.Execute())); + + return NS_OK; +} + +nsresult PerformRenameDirectory(const FileSystemConnection& aConnection, + const FileSystemEntryMetadata& aHandle, + const Name& aNewName) { + const nsLiteralCString updateDirectoryNameQuery = + "UPDATE Directories " + "SET name = :name " + "WHERE handle = :handle " + ";"_ns; + + return PerformRename(aConnection, aHandle, aNewName, + updateDirectoryNameQuery); +} + +nsresult PerformRenameFile(const FileSystemConnection& aConnection, + const FileSystemEntryMetadata& aHandle, + const Name& aNewName) { + const nsLiteralCString updateFileNameQuery = + "UPDATE Files " + "SET name = :name " + "WHERE handle = :handle " + ";"_ns; + + return PerformRename(aConnection, aHandle, aNewName, updateFileNameQuery); +} + +} // namespace + +/* static */ +Result<Usage, QMResult> FileSystemDatabaseManagerVersion001::GetFileUsage( + const FileSystemConnection& aConnection) { + const nsLiteralCString sumUsagesQuery = "SELECT sum(usage) FROM Usages;"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConnection, sumUsagesQuery)); + + QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep()); + if (!moreResults) { + return Err(QMResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR)); + } + + QM_TRY_UNWRAP(Usage totalFiles, stmt.GetUsageByColumn(/* Column */ 0u)); + + return totalFiles; +} + +nsresult FileSystemDatabaseManagerVersion001::UpdateUsageInDatabase( + const EntryId& aEntry, int64_t aNewDiskUsage) { + const nsLiteralCString updateUsageQuery = + "INSERT INTO Usages " + "( handle, usage ) " + "VALUES " + "( :handle, :usage ) " + "ON CONFLICT(handle) DO " + "UPDATE SET usage = excluded.usage " + ";"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, updateUsageQuery)); + QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, aNewDiskUsage))); + QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntry))); + QM_TRY(MOZ_TO_RESULT(stmt.Execute())); + + return NS_OK; +} + +Result<EntryId, QMResult> +FileSystemDatabaseManagerVersion001::GetOrCreateDirectory( + const FileSystemChildMetadata& aHandle, bool aCreate) { + MOZ_ASSERT(!aHandle.parentId().IsEmpty()); + + const auto& name = aHandle.childName(); + // Belt and suspenders: check here as well as in child. + if (!IsValidName(name)) { + return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)); + } + MOZ_ASSERT(!name.IsVoid() && !name.IsEmpty()); + + bool exists = true; + QM_TRY_UNWRAP(exists, DoesFileExist(mConnection, aHandle)); + + // By spec, we don't allow a file and a directory + // to have the same name and parent + if (exists) { + return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)); + } + + QM_TRY_UNWRAP(exists, DoesDirectoryExist(mConnection, aHandle)); + + // exists as directory + if (exists) { + return FindEntryId(mConnection, aHandle, false); + } + + if (!aCreate) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + const nsLiteralCString insertEntryQuery = + "INSERT OR IGNORE INTO Entries " + "( handle, parent ) " + "VALUES " + "( :handle, :parent ) " + ";"_ns; + + const nsLiteralCString insertDirectoryQuery = + "INSERT OR IGNORE INTO Directories " + "( handle, name ) " + "VALUES " + "( :handle, :name ) " + ";"_ns; + + QM_TRY_UNWRAP(EntryId entryId, GetUniqueEntryId(mConnection, aHandle)); + MOZ_ASSERT(!entryId.IsEmpty()); + + mozStorageTransaction transaction( + mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE); + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, insertEntryQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); + QM_TRY( + QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId()))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, insertDirectoryQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); + QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, name))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + QM_TRY_UNWRAP(DebugOnly<bool> doesItExistNow, + DoesDirectoryExist(mConnection, aHandle)); + MOZ_ASSERT(doesItExistNow); + + return entryId; +} + +Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetOrCreateFile( + const FileSystemChildMetadata& aHandle, bool aCreate) { + MOZ_ASSERT(!aHandle.parentId().IsEmpty()); + + const auto& name = aHandle.childName(); + // Belt and suspenders: check here as well as in child. + if (!IsValidName(name)) { + return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)); + } + MOZ_ASSERT(!name.IsVoid() && !name.IsEmpty()); + + QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle)); + + // By spec, we don't allow a file and a directory + // to have the same name and parent + if (exists) { + return Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)); + } + + QM_TRY_UNWRAP(exists, DoesFileExist(mConnection, aHandle)); + + if (exists) { + return FindEntryId(mConnection, aHandle, true); + } + + if (!aCreate) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + const nsLiteralCString insertEntryQuery = + "INSERT INTO Entries " + "( handle, parent ) " + "VALUES " + "( :handle, :parent ) " + ";"_ns; + + const nsLiteralCString insertFileQuery = + "INSERT INTO Files " + "( handle, name ) " + "VALUES " + "( :handle, :name ) " + ";"_ns; + + QM_TRY_UNWRAP(EntryId entryId, GetUniqueEntryId(mConnection, aHandle)); + MOZ_ASSERT(!entryId.IsEmpty()); + + mozStorageTransaction transaction( + mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE); + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, insertEntryQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); + QM_TRY( + QM_TO_RESULT(stmt.BindEntryIdByName("parent"_ns, aHandle.parentId()))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, insertFileQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); + QM_TRY(QM_TO_RESULT(stmt.BindNameByName("name"_ns, name))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + return entryId; +} + +Result<FileSystemDirectoryListing, QMResult> +FileSystemDatabaseManagerVersion001::GetDirectoryEntries( + const EntryId& aParent, PageNumber aPage) const { + // TODO: Offset is reported to have bad performance - see Bug 1780386. + const nsCString directoriesQuery = + "SELECT Dirs.handle, Dirs.name " + "FROM Directories AS Dirs " + "INNER JOIN ( " + "SELECT handle " + "FROM Entries " + "WHERE parent = :parent " + "LIMIT :pageSize " + "OFFSET :pageOffset ) " + "AS Ents " + "ON Dirs.handle = Ents.handle " + ";"_ns; + const nsCString filesQuery = + "SELECT Files.handle, Files.name " + "FROM Files " + "INNER JOIN ( " + "SELECT handle " + "FROM Entries " + "WHERE parent = :parent " + "LIMIT :pageSize " + "OFFSET :pageOffset ) " + "AS Ents " + "ON Files.handle = Ents.handle " + ";"_ns; + + FileSystemDirectoryListing entries; + QM_TRY( + QM_TO_RESULT(GetEntries(mConnection, directoriesQuery, aParent, aPage, + /* aDirectory */ true, entries.directories()))); + + QM_TRY(QM_TO_RESULT(GetEntries(mConnection, filesQuery, aParent, aPage, + /* aDirectory */ false, entries.files()))); + + return entries; +} + +nsresult FileSystemDatabaseManagerVersion001::GetFile( + const EntryId& aEntryId, nsString& aType, + TimeStamp& lastModifiedMilliSeconds, nsTArray<Name>& aPath, + nsCOMPtr<nsIFile>& aFile) const { + MOZ_ASSERT(!aEntryId.IsEmpty()); + + QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aEntryId)); + + QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, aEntryId, aType))); + + PRTime lastModTime = 0; + 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; +} + +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)); + if (!fileExists) { + return NS_OK; // May be deleted before update, no assert + } + + QM_TRY_UNWRAP(bool isFolder, + DoesDirectoryExist(mConnection, aEntry).mapErr(toNSResult)); + if (isFolder) { + return NS_OK; // May be deleted and replaced by a folder, no assert + } + + nsCOMPtr<nsIFile> file; + QM_TRY_UNWRAP(file, mFileManager->GetOrCreateFile(aEntry)); + MOZ_ASSERT(file); + + int64_t fileSize = 0; + QM_TRY(MOZ_TO_RESULT(file->GetFileSize(&fileSize))); + + QM_TRY(MOZ_TO_RESULT(UpdateUsageInDatabase(aEntry, fileSize))); + + return NS_OK; +} + +Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveDirectory( + const FileSystemChildMetadata& aHandle, bool aRecursive) { + MOZ_ASSERT(!aHandle.parentId().IsEmpty()); + + if (aHandle.childName().IsEmpty()) { + return false; + } + DebugOnly<Name> name = aHandle.childName(); + MOZ_ASSERT(!name.inspect().IsVoid()); + + QM_TRY_UNWRAP(bool exists, DoesDirectoryExist(mConnection, aHandle)); + + if (!exists) { + return false; + } + + // At this point, entry exists and is a directory. + QM_TRY_UNWRAP(EntryId entryId, FindEntryId(mConnection, aHandle, false)); + MOZ_ASSERT(!entryId.IsEmpty()); + + QM_TRY_UNWRAP(bool isEmpty, IsDirectoryEmpty(mConnection, entryId)); + + 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 descendantsQuery = + "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 handle " + "FROM traceChildren INNER JOIN Files " + "USING(handle) " + ";"_ns; + + const nsLiteralCString deleteEntryQuery = + "DELETE FROM Entries " + "WHERE handle = :handle " + ";"_ns; + + mozStorageTransaction transaction( + mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE); + + nsTArray<EntryId> descendants; + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, descendantsQuery)); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); + QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep()); + + while (moreResults) { + QM_TRY_UNWRAP(EntryId entryId, stmt.GetEntryIdByColumn(/* Column */ 0u)); + + descendants.AppendElement(entryId); + + QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep()); + } + } + + { + 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())); + + 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); + } + } + + return true; +} + +Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RemoveFile( + const FileSystemChildMetadata& aHandle) { + MOZ_ASSERT(!aHandle.parentId().IsEmpty()); + + if (aHandle.childName().IsEmpty()) { + return false; + } + DebugOnly<Name> name = aHandle.childName(); + MOZ_ASSERT(!name.inspect().IsVoid()); + + // Make it more evident that we won't remove directories + QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, aHandle)); + + 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()); + + // XXX This code assumes the spec question is resolved to state + // removing an in-use file should fail. If it shouldn't fail, we need to + // do something to neuter all the extant FileAccessHandles/WritableFileStreams + // that reference it + if (mDataManager->IsLocked(entryId)) { + LOG(("Trying to remove in-use file")); + return Err(QMResult(NS_ERROR_DOM_INVALID_MODIFICATION_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, + mFileManager->RemoveFile(entryId)); + + if (maybeFileSize) { + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + quotaManager->DecreaseUsageForClient( + quota::ClientMetadata{mDataManager->OriginMetadataRef(), + quota::Client::FILESYSTEM}, + *maybeFileSize); + } + + return true; +} + +Result<bool, QMResult> FileSystemDatabaseManagerVersion001::RenameEntry( + const FileSystemEntryMetadata& aHandle, const Name& aNewName) { + // Can't rename root + if (mRootEntry == aHandle.entryId()) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + // Verify the source exists + QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, aHandle.entryId()), false); + + // At this point, entry exists + if (isFile && mDataManager->IsLocked(aHandle.entryId())) { + LOG(("Trying to move in-use file")); + return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)); + } + + // Are we actually renaming? + if (aHandle.entryName() == aNewName) { + return true; + } + + // If the destination file exists, fail explicitly. + FileSystemChildMetadata destination; + QM_TRY_UNWRAP(EntryId parent, FindParent(mConnection, aHandle.entryId())); + destination.parentId() = parent; + destination.childName() = aNewName; + + QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, destination)); + if (exists) { + // If the destination file exists, check if it is in use + QM_TRY_INSPECT(const EntryId& destId, + FindEntryId(mConnection, destination, true)); + if (mDataManager->IsLocked(destId)) { + LOG(("Trying to overwrite in-use file")); + return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)); + } + + QM_TRY_UNWRAP(DebugOnly<bool> isRemoved, RemoveFile(destination)); + MOZ_ASSERT(isRemoved); + } else { + QM_TRY_UNWRAP(exists, DoesDirectoryExist(mConnection, destination)); + if (exists) { + return Err(QMResult(NS_ERROR_DOM_INVALID_MODIFICATION_ERR)); + } + } + + mozStorageTransaction transaction( + mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE); + + if (isFile) { + QM_TRY(QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, aNewName))); + } else { + QM_TRY( + QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, aNewName))); + } + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + return true; +} + +Result<bool, QMResult> FileSystemDatabaseManagerVersion001::MoveEntry( + const FileSystemEntryMetadata& aHandle, + const FileSystemChildMetadata& aNewDesignation) { + MOZ_ASSERT(!aHandle.entryId().IsEmpty()); + + const EntryId& entryId = aHandle.entryId(); + const Name& newName = aNewDesignation.childName(); + + if (mRootEntry == entryId) { + return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)); + } + + // Verify the source exists + QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId), false); + + // If the rename doesn't change the name or directory, just return success. + // XXX Needs to be added to the spec + if (IsSame(mConnection, aHandle, aNewDesignation, isFile)) { + return true; + } + + // At this point, entry exists + if (isFile && mDataManager->IsLocked(entryId)) { + LOG(("Trying to move in-use file")); + return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)); + } + + // If the destination file exists, fail explicitly. Spec author plans to + // revise the spec + QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, aNewDesignation)); + if (exists) { + QM_TRY_INSPECT(const EntryId& destId, + FindEntryId(mConnection, aNewDesignation, true)); + if (mDataManager->IsLocked(destId)) { + LOG(("Trying to overwrite in-use file")); + return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)); + } + + QM_TRY_UNWRAP(DebugOnly<bool> isRemoved, RemoveFile(aNewDesignation)); + MOZ_ASSERT(isRemoved); + } else { + QM_TRY_UNWRAP(exists, DoesDirectoryExist(mConnection, aNewDesignation)); + if (exists) { + return Err(QMResult(NS_ERROR_DOM_INVALID_MODIFICATION_ERR)); + } + } + + // To prevent cyclic paths, we check that there is no path from + // the item to be moved to the destination folder. + QM_TRY_UNWRAP( + const bool isDestinationUnderSelf, + IsAncestor(mConnection, {aHandle.entryId(), aNewDesignation.parentId()})); + if (isDestinationUnderSelf) { + return Err(QMResult(NS_ERROR_DOM_INVALID_MODIFICATION_ERR)); + } + + const nsLiteralCString updateEntryParentQuery = + "UPDATE Entries " + "SET parent = :parent " + "WHERE handle = :handle " + ";"_ns; + + mozStorageTransaction transaction( + mConnection.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE); + + { + // We always change the parent because it's simpler than checking if the + // parent needs to be changed + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(mConnection, updateEntryParentQuery)); + QM_TRY(QM_TO_RESULT( + stmt.BindEntryIdByName("parent"_ns, aNewDesignation.parentId()))); + QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, entryId))); + QM_TRY(QM_TO_RESULT(stmt.Execute())); + } + + // Are we actually renaming? + if (aHandle.entryName() == newName) { + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + return true; + } + + if (isFile) { + QM_TRY(QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, newName))); + } else { + QM_TRY(QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, newName))); + } + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + + return true; +} + +Result<Path, QMResult> FileSystemDatabaseManagerVersion001::Resolve( + const FileSystemEntryPair& aEndpoints) const { + QM_TRY_UNWRAP(Path path, ResolveReversedPath(mConnection, aEndpoints)); + // Note: if not an ancestor, returns null + + path.Reverse(); + return path; +} + +void FileSystemDatabaseManagerVersion001::Close() { mConnection->Close(); } + +} // namespace fs::data + +} // namespace mozilla::dom diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.h b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.h new file mode 100644 index 0000000000..e6edc4ca8a --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.h @@ -0,0 +1,101 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION001_H_ +#define DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION001_H_ + +#include "FileSystemDatabaseManager.h" +#include "nsString.h" + +namespace mozilla::dom::fs::data { + +class FileSystemDataManager; +class FileSystemFileManager; +using FileSystemConnection = fs::ResultConnection; + +/** + * @brief Versioned implementation of database interface enables backwards + * support after the schema has changed. Version number 0 refers to + * uninitialized database, and versions after that are sequential upgrades. + * + * To change the schema to the next version x, + * - a new implementation FileSystemDatabaseManagerVersion00x is derived from + * the previous version and the required methods are overridden + * - a new apppropriate schema initialization class SchemaVersion00x is created + * or derived + * - the factory method of FileSystemDatabaseManager is extended to try to + * migrate the data from the previous version to version x, and to return + * FileSystemDatabaseManagerVersion00x implementation if the database version + * after the migrations is x + * - note that if the migration fails at some old version, the corresponding + * old implementation should be returned: this way the users whose migrations + * fail systematically due to hardware or other issues will not get locked out + */ +class FileSystemDatabaseManagerVersion001 : public FileSystemDatabaseManager { + public: + FileSystemDatabaseManagerVersion001( + FileSystemDataManager* aDataManager, FileSystemConnection&& aConnection, + UniquePtr<FileSystemFileManager>&& aFileManager, + const EntryId& aRootEntry) + : mDataManager(aDataManager), + mConnection(aConnection), + mFileManager(std::move(aFileManager)), + mRootEntry(aRootEntry) {} + + static Result<Usage, QMResult> GetFileUsage( + const FileSystemConnection& aConnection); + + virtual nsresult UpdateUsage(const EntryId& aEntry) override; + + virtual Result<EntryId, QMResult> GetOrCreateDirectory( + const FileSystemChildMetadata& aHandle, bool aCreate) override; + + virtual Result<EntryId, QMResult> GetOrCreateFile( + const FileSystemChildMetadata& aHandle, bool aCreate) override; + + virtual nsresult GetFile(const EntryId& aEntryId, nsString& aType, + TimeStamp& lastModifiedMilliSeconds, Path& aPath, + nsCOMPtr<nsIFile>& aFile) const override; + + virtual Result<FileSystemDirectoryListing, QMResult> GetDirectoryEntries( + const EntryId& aParent, PageNumber aPage) const override; + + virtual Result<bool, QMResult> RenameEntry( + const FileSystemEntryMetadata& aHandle, const Name& aNewName) override; + + virtual Result<bool, QMResult> MoveEntry( + const FileSystemEntryMetadata& aHandle, + const FileSystemChildMetadata& aNewDesignation) override; + + virtual Result<bool, QMResult> RemoveDirectory( + const FileSystemChildMetadata& aHandle, bool aRecursive) override; + + virtual Result<bool, QMResult> RemoveFile( + const FileSystemChildMetadata& aHandle) override; + + virtual Result<Path, QMResult> Resolve( + const FileSystemEntryPair& aEndpoints) const override; + + virtual void Close() override; + + virtual ~FileSystemDatabaseManagerVersion001() = default; + + private: + nsresult UpdateUsageInDatabase(const EntryId& aEntry, int64_t aNewDiskUsage); + + // This is a raw pointer since we're owned by the FileSystemDataManager. + FileSystemDataManager* MOZ_NON_OWNING_REF mDataManager; + + FileSystemConnection mConnection; + + UniquePtr<FileSystemFileManager> mFileManager; + + const EntryId mRootEntry; +}; + +} // namespace mozilla::dom::fs::data + +#endif // DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION001_H_ diff --git a/dom/fs/parent/datamodel/FileSystemFileManager.cpp b/dom/fs/parent/datamodel/FileSystemFileManager.cpp new file mode 100644 index 0000000000..4a70e634e4 --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemFileManager.cpp @@ -0,0 +1,267 @@ +/* -*- 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 "nsIFile.h" +#include "nsIFileProtocolHandler.h" +#include "nsIFileURL.h" +#include "nsIURIMutator.h" +#include "nsXPCOM.h" + +namespace mozilla::dom::fs::data { + +namespace { + +constexpr nsLiteralString kDatabaseFileName = u"metadata.sqlite"_ns; + +Result<nsCOMPtr<nsIFile>, QMResult> GetFileDestination( + const nsCOMPtr<nsIFile>& aTopDirectory, const EntryId& aEntryId) { + MOZ_ASSERT(32u == aEntryId.Length()); + + nsCOMPtr<nsIFile> 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<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFileImpl( + const nsAString& aFilePath) { + MOZ_ASSERT(!aFilePath.IsEmpty()); + + nsCOMPtr<nsIFile> 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<nsCOMPtr<nsIFile>, QMResult> GetFile( + const nsCOMPtr<nsIFile>& aTopDirectory, const EntryId& aEntryId) { + MOZ_ASSERT(!aEntryId.IsEmpty()); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject, + GetFileDestination(aTopDirectory, aEntryId)); + + nsString desiredPath; + QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath))); + + nsCOMPtr<nsIFile> result; + QM_TRY(QM_TO_RESULT(NS_NewLocalFile(desiredPath, + /* aFollowLinks */ false, + getter_AddRefs(result)))); + + return result; +} + +Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFile( + const nsCOMPtr<nsIFile>& aTopDirectory, const EntryId& aEntryId) { + MOZ_ASSERT(!aEntryId.IsEmpty()); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject, + GetFileDestination(aTopDirectory, aEntryId)); + + nsString desiredPath; + QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath))); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result, GetOrCreateFileImpl(desiredPath)); + + return result; +} + +} // namespace + +Result<nsCOMPtr<nsIFile>, QMResult> GetFileSystemDirectory( + const Origin& aOrigin) { + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + MOZ_ASSERT(quotaManager); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileSystemDirectory, + QM_TO_RESULT_TRANSFORM(quotaManager->GetDirectoryForOrigin( + quota::PERSISTENCE_TYPE_DEFAULT, aOrigin))); + + 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<nsCOMPtr<nsIFile>, QMResult> GetDatabaseFile(const Origin& aOrigin) { + MOZ_ASSERT(!aOrigin.IsEmpty()); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile, + GetFileSystemDirectory(aOrigin)); + + QM_TRY(QM_TO_RESULT(databaseFile->AppendRelativePath(kDatabaseFileName))); + + return databaseFile; +} + +/** + * TODO: This is almost identical to the corresponding function of IndexedDB + */ +Result<nsCOMPtr<nsIFileURL>, QMResult> GetDatabaseFileURL( + const Origin& aOrigin, const int64_t aDirectoryLockId) { + MOZ_ASSERT(aDirectoryLockId >= 0); + + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile, GetDatabaseFile(aOrigin)); + + QM_TRY_INSPECT( + const auto& protocolHandler, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_GET_TYPED( + nsCOMPtr<nsIProtocolHandler>, 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<nsIFileProtocolHandler>, + MOZ_SELECT_OVERLOAD(do_QueryInterface), protocolHandler))); + + QM_TRY_INSPECT(const auto& mutator, + QM_TO_RESULT_TRANSFORM(MOZ_TO_RESULT_INVOKE_MEMBER_TYPED( + nsCOMPtr<nsIURIMutator>, fileHandler, NewFileURIMutator, + databaseFile))); + + nsCString directoryLockIdClause = "&directoryLockId="_ns; + directoryLockIdClause.AppendInt(aDirectoryLockId); + + nsCOMPtr<nsIFileURL> result; + QM_TRY(QM_TO_RESULT( + NS_MutateURI(mutator).SetQuery(directoryLockIdClause).Finalize(result))); + + return result; +} + +/* static */ +Result<FileSystemFileManager, QMResult> +FileSystemFileManager::CreateFileSystemFileManager( + nsCOMPtr<nsIFile>&& topDirectory) { + return FileSystemFileManager(std::move(topDirectory)); +} + +/* static */ +Result<FileSystemFileManager, QMResult> +FileSystemFileManager::CreateFileSystemFileManager(const Origin& aOrigin) { + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> topDirectory, + GetFileSystemDirectory(aOrigin)); + + return FileSystemFileManager(std::move(topDirectory)); +} + +FileSystemFileManager::FileSystemFileManager(nsCOMPtr<nsIFile>&& aTopDirectory) + : mTopDirectory(std::move(aTopDirectory)) {} + +Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetFile( + const EntryId& aEntryId) const { + return data::GetFile(mTopDirectory, aEntryId); +} + +Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetOrCreateFile( + const EntryId& aEntryId) { + return data::GetOrCreateFile(mTopDirectory, aEntryId); +} + +Result<int64_t, QMResult> FileSystemFileManager::RemoveFile( + const EntryId& aEntryId) { + MOZ_ASSERT(!aEntryId.IsEmpty()); + QM_TRY_UNWRAP(nsCOMPtr<nsIFile> 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))); + + if (!isFile) { + return Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY)); + } + + QM_TRY_UNWRAP(int64_t fileSize, + QM_TO_RESULT_INVOKE_MEMBER(pathObject, GetFileSize)); + + QM_TRY(QM_TO_RESULT(pathObject->Remove(/* recursive */ false))); + + return fileSize; +} + +} // namespace mozilla::dom::fs::data diff --git a/dom/fs/parent/datamodel/FileSystemFileManager.h b/dom/fs/parent/datamodel/FileSystemFileManager.h new file mode 100644 index 0000000000..9df92b04f8 --- /dev/null +++ b/dom/fs/parent/datamodel/FileSystemFileManager.h @@ -0,0 +1,147 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_DATAMODEL_FILESYSTEMFILEMANAGER_H_ +#define DOM_FS_PARENT_DATAMODEL_FILESYSTEMFILEMANAGER_H_ + +#include "ErrorList.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/QMResult.h" +#include "nsIFile.h" +#include "nsString.h" + +template <class T> +class nsCOMPtr; + +class nsIFileURL; + +namespace mozilla::dom { + +namespace quota { + +struct OriginMetadata; + +} // namespace quota + +namespace fs::data { + +/** + * @brief Get the directory for file system items of specified origin. + * Use this instead of constructing the path from quota manager's storage path. + * + * @param aOrigin Specified origin + * @return Result<nsCOMPtr<nsIFile>, QMResult> Top file system directory + */ +Result<nsCOMPtr<nsIFile>, QMResult> GetFileSystemDirectory( + const Origin& aOrigin); + +/** + * @brief Ensure that the origin-specific directory for file system exists. + * + * @param aOriginMetadata Specified origin metadata + * @return nsresult Error if operation failed + */ +nsresult EnsureFileSystemDirectory( + const quota::OriginMetadata& aOriginMetadata); + +/** + * @brief Get file system's database path for specified origin. + * Use this to get the database path instead of constructing it from + * quota manager's storage path - without the side effect of potentially + * creating it. + * + * @param aOrigin Specified origin + * @return Result<nsCOMPtr<nsIFile>, QMResult> Database file object + */ +Result<nsCOMPtr<nsIFile>, QMResult> GetDatabaseFile(const Origin& aOrigin); + +/** + * @brief Get file system's database url with directory lock parameter for + * specified origin. Use this to open a database connection and have the quota + * manager guard against its deletion or busy errors due to other connections. + * + * @param aOrigin Specified origin + * @param aDirectoryLockId Directory lock id from the quota manager + * @return Result<nsCOMPtr<nsIFileURL>, QMResult> Database file URL object + */ +Result<nsCOMPtr<nsIFileURL>, QMResult> GetDatabaseFileURL( + const Origin& aOrigin, const int64_t aDirectoryLockId); + +/** + * @brief Creates and removes disk-backed representations of the file systems' + * file entries for a specified origin. + * + * Other components should not depend on how the files are organized on disk + * but instead rely on the entry id and have access to the local file using the + * GetOrCreateFile result. + * + * The local paths are not necessarily stable in the long term and if they + * absolutely must be cached, there should be a way to repopulate the cache + * after an internal reorganization of the file entry represenations on disk, + * for some currently unforeseen maintenance reason. + * + * Example: if GetOrCreateFile used to map entryId 'abc' to path '/c/u/1/123' + * and now it maps it to '/d/u/1/12/123', the cache should either update all + * paths at once through a migration, or purge them and save a new value + * whenever a call to GetOrCreateFile is made. + */ +class FileSystemFileManager { + public: + /** + * @brief Create a File System File Manager object for a specified origin. + * + * @param aOrigin + * @return Result<FileSystemFileManager, QMResult> + */ + static Result<FileSystemFileManager, QMResult> CreateFileSystemFileManager( + const Origin& aOrigin); + + /** + * @brief Create a File System File Manager object which keeps file entries + * under a specified directory instead of quota manager's storage path. + * This should only be used for testing and preferably removed. + * + * @param topDirectory + * @return Result<FileSystemFileManager, QMResult> + */ + static Result<FileSystemFileManager, QMResult> CreateFileSystemFileManager( + nsCOMPtr<nsIFile>&& topDirectory); + + /** + * @brief Get a file object for a specified entry id. If a file for the entry + * does not exist, returns an appropriate error. + * + * @param aEntryId Specified id of a file system entry + * @return Result<nsCOMPtr<nsIFile>, QMResult> File or error. + */ + Result<nsCOMPtr<nsIFile>, QMResult> GetFile(const EntryId& aEntryId) const; + + /** + * @brief Get or create a disk-backed file object for a specified entry id. + * + * @param aEntryId Specified id of a file system entry + * @return Result<nsCOMPtr<nsIFile>, QMResult> File abstraction or IO error + */ + Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFile(const EntryId& aEntryId); + + /** + * @brief Remove the disk-backed file object for a specified entry id. + * + * @param aEntryId Specified id of a file system entry + * @return Result<int64_t, QMResult> Error or file size + */ + Result<int64_t, QMResult> RemoveFile(const EntryId& aEntryId); + + private: + explicit FileSystemFileManager(nsCOMPtr<nsIFile>&& aTopDirectory); + + nsCOMPtr<nsIFile> mTopDirectory; +}; + +} // namespace fs::data +} // namespace mozilla::dom + +#endif // DOM_FS_PARENT_DATAMODEL_FILESYSTEMFILEMANAGER_H_ diff --git a/dom/fs/parent/datamodel/SchemaVersion001.cpp b/dom/fs/parent/datamodel/SchemaVersion001.cpp new file mode 100644 index 0000000000..435b2a6dd1 --- /dev/null +++ b/dom/fs/parent/datamodel/SchemaVersion001.cpp @@ -0,0 +1,185 @@ +/* -*- 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 "SchemaVersion001.h" + +#include "FileSystemHashSource.h" +#include "ResultStatement.h" +#include "fs/FileSystemConstants.h" +#include "mozStorageHelper.h" +#include "mozilla/dom/quota/QuotaCommon.h" +#include "mozilla/dom/quota/ResultExtensions.h" + +namespace mozilla::dom::fs { + +namespace { + +nsresult SetEncoding(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL(R"(PRAGMA encoding = "UTF-16";)"_ns); +} + +nsresult CreateEntries(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL( + "CREATE TABLE IF NOT EXISTS Entries ( " + "handle BLOB PRIMARY KEY, " // Generated from parent + name, unique + "parent BLOB, " // Not null due to constraint + "CONSTRAINT parent_is_a_directory " + "FOREIGN KEY (parent) " + "REFERENCES Directories (handle) " + "ON DELETE CASCADE ) " + ";"_ns); +} + +nsresult CreateDirectories(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL( + "CREATE TABLE IF NOT EXISTS Directories ( " + "handle BLOB PRIMARY KEY, " + "name BLOB NOT NULL, " + "CONSTRAINT directories_are_entries " + "FOREIGN KEY (handle) " + "REFERENCES Entries (handle) " + "ON DELETE CASCADE ) " + ";"_ns); +} + +nsresult CreateFiles(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL( + "CREATE TABLE IF NOT EXISTS Files ( " + "handle BLOB PRIMARY KEY, " + "type TEXT, " + "name BLOB NOT NULL, " + "CONSTRAINT files_are_entries " + "FOREIGN KEY (handle) " + "REFERENCES Entries (handle) " + "ON DELETE CASCADE ) " + ";"_ns); +} + +nsresult CreateUsages(ResultConnection& aConn) { + return aConn->ExecuteSimpleSQL( + "CREATE TABLE IF NOT EXISTS Usages ( " + "handle BLOB PRIMARY KEY, " + "usage INTEGER NOT NULL DEFAULT 0, " + "tracked BOOLEAN NOT NULL DEFAULT 0 CHECK (tracked IN (0, 1)), " + "CONSTRAINT handles_are_files " + "FOREIGN KEY (handle) " + "REFERENCES Files (handle) " + "ON DELETE CASCADE ) " + ";"_ns); +} + +class KeepForeignKeysOffUntilScopeExit final { + public: + explicit KeepForeignKeysOffUntilScopeExit(const ResultConnection& aConn) + : mConn(aConn) {} + + static Result<KeepForeignKeysOffUntilScopeExit, QMResult> Create( + const ResultConnection& aConn) { + QM_TRY( + QM_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns))); + KeepForeignKeysOffUntilScopeExit result(aConn); + return result; + } + + ~KeepForeignKeysOffUntilScopeExit() { + auto maskResult = [this]() -> Result<Ok, nsresult> { + QM_TRY(MOZ_TO_RESULT( + mConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns))); + + return Ok{}; + }; + QM_WARNONLY_TRY(maskResult()); + } + + private: + ResultConnection mConn; +}; + +nsresult CreateRootEntry(ResultConnection& aConn, const Origin& aOrigin) { + KeepForeignKeysOffUntilScopeExit foreignKeysGuard(aConn); + + const nsLiteralCString createRootQuery = + "INSERT OR IGNORE INTO Entries " + "( handle, parent ) " + "VALUES ( :handle, NULL );"_ns; + + const nsLiteralCString flagRootAsDirectoryQuery = + "INSERT OR IGNORE INTO Directories " + "( handle, name ) " + "VALUES ( :handle, :name );"_ns; + + QM_TRY_UNWRAP(EntryId rootId, + data::FileSystemHashSource::GenerateHash(aOrigin, kRootName)); + + mozStorageTransaction transaction( + aConn.get(), false, mozIStorageConnection::TRANSACTION_IMMEDIATE); + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConn, createRootQuery)); + QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, rootId))); + QM_TRY(MOZ_TO_RESULT(stmt.Execute())); + } + + { + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConn, flagRootAsDirectoryQuery)); + QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, rootId))); + QM_TRY(MOZ_TO_RESULT(stmt.BindNameByName("name"_ns, kRootName))); + QM_TRY(MOZ_TO_RESULT(stmt.Execute())); + } + + return transaction.Commit(); +} + +Result<bool, QMResult> CheckIfEmpty(ResultConnection& aConn) { + const nsLiteralCString areThereTablesQuery = + "SELECT EXISTS (" + "SELECT 1 FROM sqlite_master " + ");"_ns; + + QM_TRY_UNWRAP(ResultStatement stmt, + ResultStatement::Create(aConn, areThereTablesQuery)); + + return stmt.YesOrNoQuery(); +}; + +} // namespace + +Result<DatabaseVersion, QMResult> SchemaVersion001::InitializeConnection( + ResultConnection& aConn, const Origin& aOrigin) { + QM_TRY_UNWRAP(bool isEmpty, CheckIfEmpty(aConn)); + + DatabaseVersion currentVersion = 0; + + if (isEmpty) { + QM_TRY(QM_TO_RESULT(SetEncoding(aConn))); + } else { + QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(¤tVersion))); + } + + if (currentVersion < sVersion) { + mozStorageTransaction transaction( + aConn.get(), + /* commit on complete */ false, + mozIStorageConnection::TRANSACTION_IMMEDIATE); + + QM_TRY(QM_TO_RESULT(CreateEntries(aConn))); + QM_TRY(QM_TO_RESULT(CreateDirectories(aConn))); + QM_TRY(QM_TO_RESULT(CreateFiles(aConn))); + QM_TRY(QM_TO_RESULT(CreateUsages(aConn))); + QM_TRY(QM_TO_RESULT(CreateRootEntry(aConn, aOrigin))); + QM_TRY(QM_TO_RESULT(aConn->SetSchemaVersion(sVersion))); + + QM_TRY(QM_TO_RESULT(transaction.Commit())); + } + + QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(¤tVersion))); + + return currentVersion; +} + +} // namespace mozilla::dom::fs diff --git a/dom/fs/parent/datamodel/SchemaVersion001.h b/dom/fs/parent/datamodel/SchemaVersion001.h new file mode 100644 index 0000000000..606cada50d --- /dev/null +++ b/dom/fs/parent/datamodel/SchemaVersion001.h @@ -0,0 +1,25 @@ +/* -*- 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/. */ + +#ifndef DOM_FS_PARENT_DATAMODEL_SCHEMAVERSION001_H_ +#define DOM_FS_PARENT_DATAMODEL_SCHEMAVERSION001_H_ + +#include "ResultConnection.h" +#include "mozilla/dom/FileSystemTypes.h" +#include "mozilla/dom/quota/ForwardDecls.h" + +namespace mozilla::dom::fs { + +struct SchemaVersion001 { + static Result<DatabaseVersion, QMResult> InitializeConnection( + ResultConnection& aConn, const Origin& aOrigin); + + static const DatabaseVersion sVersion = 1; +}; + +} // namespace mozilla::dom::fs + +#endif // DOM_FS_PARENT_DATAMODEL_SCHEMAVERSION001_H_ diff --git a/dom/fs/parent/datamodel/moz.build b/dom/fs/parent/datamodel/moz.build new file mode 100644 index 0000000000..111a793722 --- /dev/null +++ b/dom/fs/parent/datamodel/moz.build @@ -0,0 +1,26 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +EXPORTS.mozilla.dom += [ + "FileSystemDataManager.h", +] + +UNIFIED_SOURCES += [ + "FileSystemDatabaseManager.cpp", + "FileSystemDatabaseManagerVersion001.cpp", + "FileSystemDataManager.cpp", + "FileSystemFileManager.cpp", + "SchemaVersion001.cpp", +] + +LOCAL_INCLUDES += [ + "/dom/fs/include", + "/dom/fs/parent", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" diff --git a/dom/fs/parent/moz.build b/dom/fs/parent/moz.build new file mode 100644 index 0000000000..c8a254dcff --- /dev/null +++ b/dom/fs/parent/moz.build @@ -0,0 +1,47 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIRS += [ + "datamodel", +] + +EXPORTS.mozilla.dom += [ + "FileSystemAccessHandleParent.h", + "FileSystemManagerParent.h", + "FileSystemManagerParentFactory.h", + "FileSystemQuotaClient.h", + "FileSystemWritableFileStreamParent.h", +] + +UNIFIED_SOURCES += [ + "FileSystemAccessHandleParent.cpp", + "FileSystemHashSource.cpp", + "FileSystemManagerParent.cpp", + "FileSystemManagerParentFactory.cpp", + "FileSystemQuotaClient.cpp", + "FileSystemStreamCallbacks.cpp", + "FileSystemWritableFileStreamParent.cpp", + "ResultStatement.cpp", +] + +LOCAL_INCLUDES += [ + "/dom/fs/include", + "/dom/fs/parent/datamodel", +] + +FINAL_LIBRARY = "xul" + +include("/ipc/chromium/chromium-config.mozbuild") + +if CONFIG["COMPILE_ENVIRONMENT"]: + CbindgenHeader( + "data_encoding_ffi_generated.h", + inputs=["/dom/fs/parent/rust/data-encoding-ffi"], + ) + + EXPORTS.mozilla.dom += [ + "!data_encoding_ffi_generated.h", + ] diff --git a/dom/fs/parent/rust/data-encoding-ffi/Cargo.toml b/dom/fs/parent/rust/data-encoding-ffi/Cargo.toml new file mode 100644 index 0000000000..1a641e5c7b --- /dev/null +++ b/dom/fs/parent/rust/data-encoding-ffi/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "data-encoding-ffi" +version = "0.1.0" +license = "MPL-2.0" +authors = ["The Mozilla Project Developers"] + +[dependencies] +data-encoding = "2.2.1" +nsstring = { path = "../../../../../xpcom/rust/nsstring" } diff --git a/dom/fs/parent/rust/data-encoding-ffi/cbindgen.toml b/dom/fs/parent/rust/data-encoding-ffi/cbindgen.toml new file mode 100644 index 0000000000..37da12d3b7 --- /dev/null +++ b/dom/fs/parent/rust/data-encoding-ffi/cbindgen.toml @@ -0,0 +1,11 @@ +header = """/* 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/. */""" +autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */""" +include_guard = "DOM_FS_PARENT_RUST_DATA_ENCODING_FFI_H_" +include_version = true +braces = "SameLine" +line_length = 100 +tab_width = 2 +language = "C++" +namespaces = ["mozilla", "dom", "fs"] diff --git a/dom/fs/parent/rust/data-encoding-ffi/src/lib.rs b/dom/fs/parent/rust/data-encoding-ffi/src/lib.rs new file mode 100644 index 0000000000..74900b62ae --- /dev/null +++ b/dom/fs/parent/rust/data-encoding-ffi/src/lib.rs @@ -0,0 +1,14 @@ +/* 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/. */ + +extern crate data_encoding; +extern crate nsstring; + +use data_encoding::BASE32; +use nsstring::{nsACString, nsCString}; + +#[no_mangle] +pub extern "C" fn base32encode(unencoded: &nsACString, encoded: &mut nsCString) { + encoded.assign(&BASE32.encode(&unencoded[..])); +} |