summaryrefslogtreecommitdiffstats
path: root/dom/fs/parent
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /dom/fs/parent
parentInitial commit. (diff)
downloadfirefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz
firefox-26a029d407be480d791972afb5975cf62c9360a6.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/fs/parent')
-rw-r--r--dom/fs/parent/FileSystemAccessHandle.cpp235
-rw-r--r--dom/fs/parent/FileSystemAccessHandle.h107
-rw-r--r--dom/fs/parent/FileSystemAccessHandleControlParent.cpp45
-rw-r--r--dom/fs/parent/FileSystemAccessHandleControlParent.h43
-rw-r--r--dom/fs/parent/FileSystemAccessHandleParent.cpp37
-rw-r--r--dom/fs/parent/FileSystemAccessHandleParent.h38
-rw-r--r--dom/fs/parent/FileSystemContentTypeGuess.cpp32
-rw-r--r--dom/fs/parent/FileSystemContentTypeGuess.h23
-rw-r--r--dom/fs/parent/FileSystemHashSource.cpp67
-rw-r--r--dom/fs/parent/FileSystemHashSource.h31
-rw-r--r--dom/fs/parent/FileSystemHashStorageFunction.cpp69
-rw-r--r--dom/fs/parent/FileSystemHashStorageFunction.h25
-rw-r--r--dom/fs/parent/FileSystemManagerParent.cpp519
-rw-r--r--dom/fs/parent/FileSystemManagerParent.h93
-rw-r--r--dom/fs/parent/FileSystemManagerParentFactory.cpp114
-rw-r--r--dom/fs/parent/FileSystemManagerParentFactory.h39
-rw-r--r--dom/fs/parent/FileSystemParentTypes.h46
-rw-r--r--dom/fs/parent/FileSystemQuotaClient.cpp167
-rw-r--r--dom/fs/parent/FileSystemQuotaClient.h69
-rw-r--r--dom/fs/parent/FileSystemQuotaClientFactory.cpp49
-rw-r--r--dom/fs/parent/FileSystemQuotaClientFactory.h49
-rw-r--r--dom/fs/parent/FileSystemStreamCallbacks.cpp38
-rw-r--r--dom/fs/parent/FileSystemStreamCallbacks.h38
-rw-r--r--dom/fs/parent/FileSystemWritableFileStreamParent.cpp85
-rw-r--r--dom/fs/parent/FileSystemWritableFileStreamParent.h62
-rw-r--r--dom/fs/parent/ResultConnection.h19
-rw-r--r--dom/fs/parent/ResultStatement.cpp23
-rw-r--r--dom/fs/parent/ResultStatement.h173
-rw-r--r--dom/fs/parent/StartedTransaction.cpp35
-rw-r--r--dom/fs/parent/StartedTransaction.h39
-rw-r--r--dom/fs/parent/datamodel/FileSystemDataManager.cpp672
-rw-r--r--dom/fs/parent/datamodel/FileSystemDataManager.h186
-rw-r--r--dom/fs/parent/datamodel/FileSystemDatabaseManager.cpp101
-rw-r--r--dom/fs/parent/datamodel/FileSystemDatabaseManager.h229
-rw-r--r--dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.cpp1567
-rw-r--r--dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.h208
-rw-r--r--dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.cpp832
-rw-r--r--dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.h75
-rw-r--r--dom/fs/parent/datamodel/FileSystemFileManager.cpp393
-rw-r--r--dom/fs/parent/datamodel/FileSystemFileManager.h175
-rw-r--r--dom/fs/parent/datamodel/SchemaVersion001.cpp193
-rw-r--r--dom/fs/parent/datamodel/SchemaVersion001.h31
-rw-r--r--dom/fs/parent/datamodel/SchemaVersion002.cpp618
-rw-r--r--dom/fs/parent/datamodel/SchemaVersion002.h28
-rw-r--r--dom/fs/parent/datamodel/moz.build28
-rw-r--r--dom/fs/parent/moz.build63
-rw-r--r--dom/fs/parent/rust/data-encoding-ffi/Cargo.toml9
-rw-r--r--dom/fs/parent/rust/data-encoding-ffi/cbindgen.toml11
-rw-r--r--dom/fs/parent/rust/data-encoding-ffi/src/lib.rs14
-rw-r--r--dom/fs/parent/rust/mime-guess-ffi/Cargo.toml10
-rw-r--r--dom/fs/parent/rust/mime-guess-ffi/cbindgen.toml11
-rw-r--r--dom/fs/parent/rust/mime-guess-ffi/src/lib.rs34
52 files changed, 7897 insertions, 0 deletions
diff --git a/dom/fs/parent/FileSystemAccessHandle.cpp b/dom/fs/parent/FileSystemAccessHandle.cpp
new file mode 100644
index 0000000000..07430ce476
--- /dev/null
+++ b/dom/fs/parent/FileSystemAccessHandle.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 "FileSystemAccessHandle.h"
+
+#include "FileSystemDatabaseManager.h"
+#include "FileSystemParentTypes.h"
+#include "mozilla/Result.h"
+#include "mozilla/dom/FileSystemDataManager.h"
+#include "mozilla/dom/FileSystemHelpers.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/quota/FileStreams.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/RemoteQuotaObjectParent.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "mozilla/ipc/RandomAccessStreamParams.h"
+#include "mozilla/ipc/RandomAccessStreamUtils.h"
+#include "nsIFileStreams.h"
+
+namespace mozilla::dom {
+
+FileSystemAccessHandle::FileSystemAccessHandle(
+ RefPtr<fs::data::FileSystemDataManager> aDataManager,
+ const fs::EntryId& aEntryId, MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue)
+ : mEntryId(aEntryId),
+ mDataManager(std::move(aDataManager)),
+ mIOTaskQueue(std::move(aIOTaskQueue)),
+ mActor(nullptr),
+ mControlActor(nullptr),
+ mRegCount(0),
+ mLocked(false),
+ mRegistered(false),
+ mClosed(false) {}
+
+FileSystemAccessHandle::~FileSystemAccessHandle() {
+ MOZ_DIAGNOSTIC_ASSERT(mClosed);
+}
+
+// static
+RefPtr<FileSystemAccessHandle::CreatePromise> FileSystemAccessHandle::Create(
+ RefPtr<fs::data::FileSystemDataManager> aDataManager,
+ const fs::EntryId& aEntryId) {
+ MOZ_ASSERT(aDataManager);
+ aDataManager->AssertIsOnIOTarget();
+
+ RefPtr<TaskQueue> ioTaskQueue = TaskQueue::Create(
+ do_AddRef(aDataManager->MutableIOTargetPtr()), "FileSystemAccessHandle");
+
+ RefPtr<FileSystemAccessHandle> accessHandle = new FileSystemAccessHandle(
+ std::move(aDataManager), aEntryId, WrapMovingNotNull(ioTaskQueue));
+
+ return accessHandle->BeginInit()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [accessHandle = fs::Registered<FileSystemAccessHandle>(accessHandle)](
+ InitPromise::ResolveOrRejectValue&& value) mutable {
+ if (value.IsReject()) {
+ return CreatePromise::CreateAndReject(value.RejectValue(), __func__);
+ }
+
+ mozilla::ipc::RandomAccessStreamParams streamParams =
+ std::move(value.ResolveValue());
+
+ return CreatePromise::CreateAndResolve(
+ std::pair(std::move(accessHandle), std::move(streamParams)),
+ __func__);
+ });
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(FileSystemAccessHandle, FileSystemStreamCallbacks)
+
+void FileSystemAccessHandle::Register() { ++mRegCount; }
+
+void FileSystemAccessHandle::Unregister() {
+ MOZ_ASSERT(mRegCount > 0);
+
+ --mRegCount;
+
+ if (IsInactive() && IsOpen()) {
+ BeginClose();
+ }
+}
+
+void FileSystemAccessHandle::RegisterActor(
+ NotNull<FileSystemAccessHandleParent*> aActor) {
+ MOZ_ASSERT(!mActor);
+
+ mActor = aActor;
+}
+
+void FileSystemAccessHandle::UnregisterActor(
+ NotNull<FileSystemAccessHandleParent*> aActor) {
+ MOZ_ASSERT(mActor);
+ MOZ_ASSERT(mActor == aActor);
+
+ mActor = nullptr;
+
+ if (IsInactive() && IsOpen()) {
+ BeginClose();
+ }
+}
+
+void FileSystemAccessHandle::RegisterControlActor(
+ NotNull<FileSystemAccessHandleControlParent*> aControlActor) {
+ MOZ_ASSERT(!mControlActor);
+
+ mControlActor = aControlActor;
+}
+
+void FileSystemAccessHandle::UnregisterControlActor(
+ NotNull<FileSystemAccessHandleControlParent*> aControlActor) {
+ MOZ_ASSERT(mControlActor);
+ MOZ_ASSERT(mControlActor == aControlActor);
+
+ mControlActor = nullptr;
+
+ if (IsInactive() && IsOpen()) {
+ BeginClose();
+ }
+}
+
+bool FileSystemAccessHandle::IsOpen() const { return !mClosed; }
+
+RefPtr<BoolPromise> FileSystemAccessHandle::BeginClose() {
+ MOZ_ASSERT(IsOpen());
+
+ LOG(("Closing AccessHandle"));
+
+ mClosed = true;
+
+ return InvokeAsync(mIOTaskQueue.get(), __func__,
+ [self = RefPtr(this)]() {
+ if (self->mRemoteQuotaObjectParent) {
+ self->mRemoteQuotaObjectParent->Close();
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) {
+ return self->mIOTaskQueue->BeginShutdown();
+ })
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this)](const ShutdownPromise::ResolveOrRejectValue&) {
+ if (self->mLocked) {
+ self->mDataManager->UnlockExclusive(self->mEntryId);
+ }
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(mDataManager->MutableBackgroundTargetPtr(), __func__,
+ [self = RefPtr(this)](const BoolPromise::ResolveOrRejectValue&) {
+ if (self->mRegistered) {
+ self->mDataManager->UnregisterAccessHandle(WrapNotNull(self));
+ }
+
+ self->mDataManager = nullptr;
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ });
+}
+
+bool FileSystemAccessHandle::IsInactive() const {
+ return !mRegCount && !mActor && !mControlActor;
+}
+
+RefPtr<FileSystemAccessHandle::InitPromise>
+FileSystemAccessHandle::BeginInit() {
+ QM_TRY_UNWRAP(fs::FileId fileId, mDataManager->LockExclusive(mEntryId),
+ [](const auto& aRv) {
+ return InitPromise::CreateAndReject(ToNSResult(aRv),
+ __func__);
+ });
+
+ mLocked = true;
+
+ auto CreateAndRejectInitPromise = [](const char* aFunc, nsresult aRv) {
+ return CreateAndRejectMozPromise<InitPromise>(aFunc, aRv);
+ };
+
+ fs::ContentType type;
+ fs::TimeStamp lastModifiedMilliSeconds;
+ fs::Path path;
+ nsCOMPtr<nsIFile> file;
+ QM_TRY(MOZ_TO_RESULT(mDataManager->MutableDatabaseManagerPtr()->GetFile(
+ mEntryId, fileId, fs::FileMode::EXCLUSIVE, type,
+ lastModifiedMilliSeconds, path, file)),
+ CreateAndRejectInitPromise);
+
+ if (LOG_ENABLED()) {
+ nsAutoString path;
+ if (NS_SUCCEEDED(file->GetPath(path))) {
+ LOG(("Opening SyncAccessHandle %s", NS_ConvertUTF16toUTF8(path).get()));
+ }
+ }
+
+ return InvokeAsync(
+ mDataManager->MutableBackgroundTargetPtr(), __func__,
+ [self = RefPtr(this)]() {
+ self->mDataManager->RegisterAccessHandle(WrapNotNull(self));
+
+ self->mRegistered = true;
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(mIOTaskQueue.get(), __func__,
+ [self = RefPtr(this), CreateAndRejectInitPromise,
+ file = std::move(file)](
+ const BoolPromise::ResolveOrRejectValue& value) {
+ if (value.IsReject()) {
+ return InitPromise::CreateAndReject(value.RejectValue(),
+ __func__);
+ }
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIRandomAccessStream> stream,
+ CreateFileRandomAccessStream(
+ quota::PERSISTENCE_TYPE_DEFAULT,
+ self->mDataManager->OriginMetadataRef(),
+ quota::Client::FILESYSTEM, file, -1, -1,
+ nsIFileRandomAccessStream::DEFER_OPEN),
+ CreateAndRejectInitPromise);
+
+ mozilla::ipc::RandomAccessStreamParams streamParams =
+ mozilla::ipc::SerializeRandomAccessStream(
+ WrapMovingNotNullUnchecked(std::move(stream)), self);
+
+ return InitPromise::CreateAndResolve(std::move(streamParams),
+ __func__);
+ });
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/parent/FileSystemAccessHandle.h b/dom/fs/parent/FileSystemAccessHandle.h
new file mode 100644
index 0000000000..74b177d04d
--- /dev/null
+++ b/dom/fs/parent/FileSystemAccessHandle.h
@@ -0,0 +1,107 @@
+/* -*- 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_FILESYSTEMACCESSHANDLE_H_
+#define DOM_FS_PARENT_FILESYSTEMACCESSHANDLE_H_
+
+#include "FileSystemStreamCallbacks.h"
+#include "mozilla/MozPromise.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/dom/FileSystemTypes.h"
+#include "mozilla/dom/quota/ForwardDecls.h"
+#include "nsISupportsImpl.h"
+#include "nsString.h"
+
+enum class nsresult : uint32_t;
+
+namespace mozilla {
+
+class TaskQueue;
+
+namespace ipc {
+class RandomAccessStreamParams;
+}
+
+namespace dom {
+
+class FileSystemAccessHandleControlParent;
+class FileSystemAccessHandleParent;
+
+namespace fs {
+
+template <class T>
+class Registered;
+
+namespace data {
+
+class FileSystemDataManager;
+
+} // namespace data
+} // namespace fs
+
+class FileSystemAccessHandle : public FileSystemStreamCallbacks {
+ public:
+ using CreateResult = std::pair<fs::Registered<FileSystemAccessHandle>,
+ mozilla::ipc::RandomAccessStreamParams>;
+ // IsExclusive is true because we want to allow moving of CreateResult.
+ // There's always just one consumer anyway (When IsExclusive is true, there
+ // can be at most one call to either Then or ChainTo).
+ using CreatePromise = MozPromise<CreateResult, nsresult,
+ /* IsExclusive */ true>;
+ static RefPtr<CreatePromise> Create(
+ RefPtr<fs::data::FileSystemDataManager> aDataManager,
+ const fs::EntryId& aEntryId);
+
+ NS_DECL_ISUPPORTS_INHERITED
+
+ void Register();
+
+ void Unregister();
+
+ void RegisterActor(NotNull<FileSystemAccessHandleParent*> aActor);
+
+ void UnregisterActor(NotNull<FileSystemAccessHandleParent*> aActor);
+
+ void RegisterControlActor(
+ NotNull<FileSystemAccessHandleControlParent*> aControlActor);
+
+ void UnregisterControlActor(
+ NotNull<FileSystemAccessHandleControlParent*> aControlActor);
+
+ bool IsOpen() const;
+
+ RefPtr<BoolPromise> BeginClose();
+
+ private:
+ FileSystemAccessHandle(RefPtr<fs::data::FileSystemDataManager> aDataManager,
+ const fs::EntryId& aEntryId,
+ MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue);
+
+ ~FileSystemAccessHandle();
+
+ bool IsInactive() const;
+
+ using InitPromise =
+ MozPromise<mozilla::ipc::RandomAccessStreamParams, nsresult,
+ /* IsExclusive */ true>;
+ RefPtr<InitPromise> BeginInit();
+
+ const fs::EntryId mEntryId;
+ RefPtr<fs::data::FileSystemDataManager> mDataManager;
+ const NotNull<RefPtr<TaskQueue>> mIOTaskQueue;
+ FileSystemAccessHandleParent* mActor;
+ FileSystemAccessHandleControlParent* mControlActor;
+ nsAutoRefCnt mRegCount;
+ bool mLocked;
+ bool mRegistered;
+ bool mClosed;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // DOM_FS_PARENT_FILESYSTEMACCESSHANDLE_H_
diff --git a/dom/fs/parent/FileSystemAccessHandleControlParent.cpp b/dom/fs/parent/FileSystemAccessHandleControlParent.cpp
new file mode 100644
index 0000000000..90f325aeb5
--- /dev/null
+++ b/dom/fs/parent/FileSystemAccessHandleControlParent.cpp
@@ -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/. */
+
+#include "FileSystemAccessHandleControlParent.h"
+
+#include "mozilla/dom/FileSystemAccessHandle.h"
+#include "mozilla/ipc/IPCCore.h"
+
+namespace mozilla::dom {
+
+FileSystemAccessHandleControlParent::FileSystemAccessHandleControlParent(
+ RefPtr<FileSystemAccessHandle> aAccessHandle)
+ : mAccessHandle(std::move(aAccessHandle)) {}
+
+FileSystemAccessHandleControlParent::~FileSystemAccessHandleControlParent() {
+ MOZ_ASSERT(mActorDestroyed);
+}
+
+mozilla::ipc::IPCResult FileSystemAccessHandleControlParent::RecvClose(
+ CloseResolver&& aResolver) {
+ mAccessHandle->BeginClose()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [resolver = std::move(aResolver)](
+ const BoolPromise::ResolveOrRejectValue&) { resolver(void_t()); });
+
+ return IPC_OK();
+}
+
+void FileSystemAccessHandleControlParent::ActorDestroy(
+ ActorDestroyReason /* aWhy */) {
+ MOZ_ASSERT(!mActorDestroyed);
+
+#ifdef DEBUG
+ mActorDestroyed = true;
+#endif
+
+ mAccessHandle->UnregisterControlActor(WrapNotNullUnchecked(this));
+
+ mAccessHandle = nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/parent/FileSystemAccessHandleControlParent.h b/dom/fs/parent/FileSystemAccessHandleControlParent.h
new file mode 100644
index 0000000000..80e76a6e53
--- /dev/null
+++ b/dom/fs/parent/FileSystemAccessHandleControlParent.h
@@ -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/. */
+
+#ifndef DOM_FS_PARENT_FILESYSTEMACCESSHANDLECONTROLPARENT_H_
+#define DOM_FS_PARENT_FILESYSTEMACCESSHANDLECONTROLPARENT_H_
+
+#include "mozilla/dom/PFileSystemAccessHandleControlParent.h"
+#include "nsISupportsUtils.h"
+
+namespace mozilla::dom {
+
+class FileSystemAccessHandle;
+
+class FileSystemAccessHandleControlParent
+ : public PFileSystemAccessHandleControlParent {
+ public:
+ explicit FileSystemAccessHandleControlParent(
+ RefPtr<FileSystemAccessHandle> aAccessHandle);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemAccessHandleControlParent,
+ override)
+
+ mozilla::ipc::IPCResult RecvClose(CloseResolver&& aResolver);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ protected:
+ virtual ~FileSystemAccessHandleControlParent();
+
+ private:
+ RefPtr<FileSystemAccessHandle> mAccessHandle;
+
+#ifdef DEBUG
+ bool mActorDestroyed = false;
+#endif
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_FS_PARENT_FILESYSTEMACCESSHANDLECONTROLPARENT_H_
diff --git a/dom/fs/parent/FileSystemAccessHandleParent.cpp b/dom/fs/parent/FileSystemAccessHandleParent.cpp
new file mode 100644
index 0000000000..36d21fdd2d
--- /dev/null
+++ b/dom/fs/parent/FileSystemAccessHandleParent.cpp
@@ -0,0 +1,37 @@
+/* -*- 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 "mozilla/dom/FileSystemAccessHandle.h"
+
+namespace mozilla::dom {
+
+FileSystemAccessHandleParent::FileSystemAccessHandleParent(
+ RefPtr<FileSystemAccessHandle> aAccessHandle)
+ : mAccessHandle(std::move(aAccessHandle)) {}
+
+FileSystemAccessHandleParent::~FileSystemAccessHandleParent() {
+ MOZ_ASSERT(mActorDestroyed);
+}
+
+mozilla::ipc::IPCResult FileSystemAccessHandleParent::RecvClose() {
+ mAccessHandle->BeginClose();
+
+ return IPC_OK();
+}
+
+void FileSystemAccessHandleParent::ActorDestroy(ActorDestroyReason aWhy) {
+ MOZ_ASSERT(!mActorDestroyed);
+
+ DEBUGONLY(mActorDestroyed = true);
+
+ mAccessHandle->UnregisterActor(WrapNotNullUnchecked(this));
+
+ mAccessHandle = nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/parent/FileSystemAccessHandleParent.h b/dom/fs/parent/FileSystemAccessHandleParent.h
new file mode 100644
index 0000000000..0efeab2539
--- /dev/null
+++ b/dom/fs/parent/FileSystemAccessHandleParent.h
@@ -0,0 +1,38 @@
+/* -*- 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/PFileSystemAccessHandleParent.h"
+#include "mozilla/dom/quota/DebugOnlyMacro.h"
+
+namespace mozilla::dom {
+
+class FileSystemAccessHandle;
+
+class FileSystemAccessHandleParent : public PFileSystemAccessHandleParent {
+ public:
+ explicit FileSystemAccessHandleParent(
+ RefPtr<FileSystemAccessHandle> aAccessHandle);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemAccessHandleParent, override)
+
+ mozilla::ipc::IPCResult RecvClose();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ private:
+ virtual ~FileSystemAccessHandleParent();
+
+ RefPtr<FileSystemAccessHandle> mAccessHandle;
+
+ DEBUGONLY(bool mActorDestroyed = false);
+};
+
+} // namespace mozilla::dom
+
+#endif // DOM_FS_PARENT_FILESYSTEMACCESSHANDLEPARENT_H_
diff --git a/dom/fs/parent/FileSystemContentTypeGuess.cpp b/dom/fs/parent/FileSystemContentTypeGuess.cpp
new file mode 100644
index 0000000000..e8ff8a760b
--- /dev/null
+++ b/dom/fs/parent/FileSystemContentTypeGuess.cpp
@@ -0,0 +1,32 @@
+/* -*- 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 "FileSystemContentTypeGuess.h"
+
+#include "ErrorList.h"
+#include "mozilla/dom/QMResult.h"
+#include "mozilla/dom/mime_guess_ffi_generated.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsString.h"
+
+namespace mozilla::dom::fs {
+
+Result<ContentType, QMResult> FileSystemContentTypeGuess::FromPath(
+ const Name& aPath) {
+ NS_ConvertUTF16toUTF8 path(aPath);
+ ContentType contentType;
+ nsresult rv = mimeGuessFromPath(&path, &contentType);
+
+ // QM_TRY is too verbose.
+ if (NS_FAILED(rv)) {
+ return Err(QMResult(rv));
+ }
+
+ return contentType;
+}
+
+} // namespace mozilla::dom::fs
diff --git a/dom/fs/parent/FileSystemContentTypeGuess.h b/dom/fs/parent/FileSystemContentTypeGuess.h
new file mode 100644
index 0000000000..8b6d13f78c
--- /dev/null
+++ b/dom/fs/parent/FileSystemContentTypeGuess.h
@@ -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/. */
+
+#ifndef DOM_FS_PARENT_FILESYSTEMCONTENTTYPEGUESS_H_
+#define DOM_FS_PARENT_FILESYSTEMCONTENTTYPEGUESS_H_
+
+#include "mozilla/Result.h"
+#include "mozilla/dom/FileSystemTypes.h"
+#include "mozilla/dom/quota/ForwardDecls.h"
+#include "nsStringFwd.h"
+
+namespace mozilla::dom::fs {
+
+struct FileSystemContentTypeGuess {
+ static Result<ContentType, QMResult> FromPath(const Name& aPath);
+};
+
+} // namespace mozilla::dom::fs
+
+#endif // DOM_FS_PARENT_FILESYSTEMCONTENTTYPEGUESS_H_
diff --git a/dom/fs/parent/FileSystemHashSource.cpp b/dom/fs/parent/FileSystemHashSource.cpp
new file mode 100644
index 0000000000..67a5a79c8c
--- /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 "FileSystemParentTypes.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 FileId& aFileId) {
+ MOZ_ASSERT(32u == aFileId.Value().Length());
+ nsCString encoded;
+ base32encode(&aFileId.Value(), &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..722500342d
--- /dev/null
+++ b/dom/fs/parent/FileSystemHashSource.h
@@ -0,0 +1,31 @@
+/* -*- 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 {
+
+struct FileId;
+
+namespace data {
+
+struct FileSystemHashSource {
+ static Result<EntryId, QMResult> GenerateHash(const EntryId& aParent,
+ const Name& aName);
+
+ static Result<Name, QMResult> EncodeHash(const FileId& aFileId);
+};
+
+} // namespace data
+} // namespace mozilla::dom::fs
+
+#endif // DOM_FS_PARENT_FILESYSTEMHASHSOURCE_H_
diff --git a/dom/fs/parent/FileSystemHashStorageFunction.cpp b/dom/fs/parent/FileSystemHashStorageFunction.cpp
new file mode 100644
index 0000000000..601f66b9c7
--- /dev/null
+++ b/dom/fs/parent/FileSystemHashStorageFunction.cpp
@@ -0,0 +1,69 @@
+/* -*- 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 "FileSystemHashStorageFunction.h"
+
+#include "FileSystemHashSource.h"
+#include "mozilla/dom/FileSystemTypes.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/storage/Variant.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+
+namespace mozilla::dom::fs::data {
+
+NS_IMPL_ISUPPORTS(FileSystemHashStorageFunction, mozIStorageFunction)
+
+NS_IMETHODIMP
+FileSystemHashStorageFunction::OnFunctionCall(
+ mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) {
+ MOZ_ASSERT(aFunctionArguments);
+ MOZ_ASSERT(aResult);
+
+ const int32_t parentIndex = 0;
+ const int32_t childIndex = 1;
+
+#ifdef DEBUG
+ {
+ uint32_t argCount;
+ MOZ_ALWAYS_SUCCEEDS(aFunctionArguments->GetNumEntries(&argCount));
+ MOZ_ASSERT(argCount == 2u);
+
+ int32_t parentType = mozIStorageValueArray::VALUE_TYPE_INTEGER;
+ MOZ_ALWAYS_SUCCEEDS(
+ aFunctionArguments->GetTypeOfIndex(parentIndex, &parentType));
+ MOZ_ASSERT(parentType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+
+ int32_t childType = mozIStorageValueArray::VALUE_TYPE_INTEGER;
+ MOZ_ALWAYS_SUCCEEDS(
+ aFunctionArguments->GetTypeOfIndex(childIndex, &childType));
+ MOZ_ASSERT(childType == mozIStorageValueArray::VALUE_TYPE_BLOB);
+ }
+#endif
+
+ QM_TRY_INSPECT(
+ const EntryId& parentHash,
+ MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(nsCString, aFunctionArguments,
+ GetBlobAsUTF8String, parentIndex));
+
+ QM_TRY_INSPECT(const Name& childName, MOZ_TO_RESULT_INVOKE_MEMBER_TYPED(
+ nsString, aFunctionArguments,
+ GetBlobAsString, childIndex));
+
+ QM_TRY_INSPECT(const EntryId& buffer,
+ FileSystemHashSource::GenerateHash(parentHash, childName)
+ .mapErr([](const auto& aRv) { return ToNSResult(aRv); }));
+
+ nsCOMPtr<nsIVariant> result =
+ new mozilla::storage::BlobVariant(std::make_pair(
+ static_cast<const void*>(buffer.get()), int(buffer.Length())));
+
+ result.forget(aResult);
+
+ return NS_OK;
+}
+
+} // namespace mozilla::dom::fs::data
diff --git a/dom/fs/parent/FileSystemHashStorageFunction.h b/dom/fs/parent/FileSystemHashStorageFunction.h
new file mode 100644
index 0000000000..7f7f9aa26c
--- /dev/null
+++ b/dom/fs/parent/FileSystemHashStorageFunction.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_FILESYSTEMHASHSTORAGEFUNCTION_H_
+#define DOM_FS_PARENT_FILESYSTEMHASHSTORAGEFUNCTION_H_
+
+#include "mozIStorageFunction.h"
+#include "nsISupportsImpl.h"
+
+namespace mozilla::dom::fs::data {
+
+class FileSystemHashStorageFunction final : public mozIStorageFunction {
+ private:
+ ~FileSystemHashStorageFunction() = default;
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_MOZISTORAGEFUNCTION
+};
+
+} // namespace mozilla::dom::fs::data
+
+#endif // DOM_FS_PARENT_FILESYSTEMHASHSTORAGEFUNCTION_H_
diff --git a/dom/fs/parent/FileSystemManagerParent.cpp b/dom/fs/parent/FileSystemManagerParent.cpp
new file mode 100644
index 0000000000..6f59c179f9
--- /dev/null
+++ b/dom/fs/parent/FileSystemManagerParent.cpp
@@ -0,0 +1,519 @@
+/* -*- 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 "FileSystemParentTypes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/FileBlobImpl.h"
+#include "mozilla/dom/FileSystemAccessHandle.h"
+#include "mozilla/dom/FileSystemAccessHandleControlParent.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));
+ MOZ_ASSERT(!mRegistered);
+}
+
+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);
+
+ EntryId entryId = aRequest.entryId();
+
+ FileSystemAccessHandle::Create(mDataManager, entryId)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr(this), request = std::move(aRequest),
+ resolver = std::move(aResolver)](
+ FileSystemAccessHandle::CreatePromise::ResolveOrRejectValue&&
+ aValue) {
+ if (!self->CanSend()) {
+ return;
+ }
+
+ if (aValue.IsReject()) {
+ resolver(aValue.RejectValue());
+ return;
+ }
+
+ FileSystemAccessHandle::CreateResult result =
+ std::move(aValue.ResolveValue());
+
+ fs::Registered<FileSystemAccessHandle> accessHandle =
+ std::move(result.first);
+
+ RandomAccessStreamParams streamParams = std::move(result.second);
+
+ auto accessHandleParent = MakeRefPtr<FileSystemAccessHandleParent>(
+ accessHandle.inspect());
+
+ auto resolveAndReturn = [&resolver](nsresult rv) { resolver(rv); };
+
+ ManagedEndpoint<PFileSystemAccessHandleChild>
+ accessHandleChildEndpoint =
+ self->OpenPFileSystemAccessHandleEndpoint(
+ accessHandleParent);
+ QM_TRY(MOZ_TO_RESULT(accessHandleChildEndpoint.IsValid()),
+ resolveAndReturn);
+
+ accessHandle->RegisterActor(WrapNotNull(accessHandleParent));
+
+ auto accessHandleControlParent =
+ MakeRefPtr<FileSystemAccessHandleControlParent>(
+ accessHandle.inspect());
+
+ Endpoint<PFileSystemAccessHandleControlParent>
+ accessHandleControlParentEndpoint;
+ Endpoint<PFileSystemAccessHandleControlChild>
+ accessHandleControlChildEndpoint;
+ MOZ_ALWAYS_SUCCEEDS(PFileSystemAccessHandleControl::CreateEndpoints(
+ &accessHandleControlParentEndpoint,
+ &accessHandleControlChildEndpoint));
+
+ accessHandleControlParentEndpoint.Bind(accessHandleControlParent);
+
+ accessHandle->RegisterControlActor(
+ WrapNotNull(accessHandleControlParent));
+
+ resolver(FileSystemAccessHandleProperties(
+ std::move(streamParams), std::move(accessHandleChildEndpoint),
+ std::move(accessHandleControlChildEndpoint)));
+ });
+
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult FileSystemManagerParent::RecvGetWritable(
+ FileSystemGetWritableRequest&& aRequest, GetWritableResolver&& aResolver) {
+ AssertIsOnIOTarget();
+ MOZ_ASSERT(mDataManager);
+
+ const fs::FileMode mode = mDataManager->GetMode(aRequest.keepData());
+
+ auto reportError = [aResolver](const auto& aRv) {
+ aResolver(ToNSResult(aRv));
+ };
+
+ // TODO: Get rid of mode and switching based on it, have the right unlocking
+ // automatically
+ const fs::EntryId& entryId = aRequest.entryId();
+ QM_TRY_UNWRAP(
+ fs::FileId fileId,
+ (mode == fs::FileMode::EXCLUSIVE ? mDataManager->LockExclusive(entryId)
+ : mDataManager->LockShared(entryId)),
+ IPC_OK(), reportError);
+ MOZ_ASSERT(!fileId.IsEmpty());
+
+ auto autoUnlock = MakeScopeExit(
+ [self = RefPtr<FileSystemManagerParent>(this), &entryId, &fileId, mode] {
+ if (mode == fs::FileMode::EXCLUSIVE) {
+ self->mDataManager->UnlockExclusive(entryId);
+ } else {
+ self->mDataManager->UnlockShared(entryId, fileId, /* aAbort */ true);
+ }
+ });
+
+ fs::ContentType type;
+ fs::TimeStamp lastModifiedMilliSeconds;
+ fs::Path path;
+ nsCOMPtr<nsIFile> file;
+ QM_TRY(
+ MOZ_TO_RESULT(mDataManager->MutableDatabaseManagerPtr()->GetFile(
+ entryId, fileId, mode, 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()));
+ }
+ }
+
+ auto writableFileStreamParent =
+ MakeNotNull<RefPtr<FileSystemWritableFileStreamParent>>(
+ this, aRequest.entryId(), fileId, mode == fs::FileMode::EXCLUSIVE);
+
+ 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);
+
+ RandomAccessStreamParams streamParams =
+ mozilla::ipc::SerializeRandomAccessStream(
+ WrapMovingNotNullUnchecked(std::move(stream)),
+ writableFileStreamParent->GetOrCreateStreamCallbacks());
+
+ // 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(std::move(streamParams),
+ writableFileStreamParent));
+
+ 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](const auto& rv) {
+ LOG(("getFile() Failed!"));
+ aResolver(ToNSResult(rv));
+ };
+
+ const auto& entryId = aRequest.entryId();
+
+ QM_TRY_INSPECT(
+ const fs::FileId& fileId,
+ mDataManager->MutableDatabaseManagerPtr()->EnsureFileId(entryId),
+ IPC_OK(), reportError);
+
+ fs::ContentType type;
+ fs::TimeStamp lastModifiedMilliSeconds;
+ fs::Path path;
+ nsCOMPtr<nsIFile> fileObject;
+ QM_TRY(MOZ_TO_RESULT(mDataManager->MutableDatabaseManagerPtr()->GetFile(
+ entryId, fileId, fs::FileMode::EXCLUSIVE, 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()));
+ }
+ }
+
+ // TODO: Currently, there is no way to assign type and it is empty.
+ // See bug 1826780.
+ RefPtr<BlobImpl> blob = MakeRefPtr<FileBlobImpl>(
+ fileObject, path.LastElement(), NS_ConvertUTF8toUTF16(type));
+
+ 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(void_t{});
+ 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(void_t{});
+ 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_INSPECT(const EntryId& newId,
+ mDataManager->MutableDatabaseManagerPtr()->MoveEntry(
+ aRequest.handle(), aRequest.destHandle()),
+ IPC_OK(), reportError);
+
+ fs::FileSystemMoveEntryResponse response(newId);
+ 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_INSPECT(const EntryId& newId,
+ mDataManager->MutableDatabaseManagerPtr()->RenameEntry(
+ aRequest.handle(), aRequest.name()),
+ IPC_OK(), reportError);
+
+ fs::FileSystemMoveEntryResponse response(newId);
+ aResolver(response);
+ return IPC_OK();
+}
+
+void FileSystemManagerParent::RequestAllowToClose() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (mRequestedAllowToClose) {
+ return;
+ }
+
+ mRequestedAllowToClose.Flip();
+
+ InvokeAsync(mDataManager->MutableIOTaskQueuePtr(), __func__,
+ [self = RefPtr<FileSystemManagerParent>(this)]() {
+ return self->SendCloseAll();
+ })
+ ->Then(mDataManager->MutableIOTaskQueuePtr(), __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);
+
+ DEBUGONLY(mActorDestroyed = true);
+
+ 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..01f9f23f6b
--- /dev/null
+++ b/dom/fs/parent/FileSystemManagerParent.h
@@ -0,0 +1,93 @@
+/* -*- 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 "mozilla/dom/quota/DebugOnlyMacro.h"
+#include "nsISupports.h"
+
+namespace mozilla::dom {
+
+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;
+
+#ifdef DEBUG
+ void SetRegistered(bool aRegistered) { mRegistered = aRegistered; }
+#endif
+
+ // 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);
+
+ void RequestAllowToClose();
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ protected:
+ virtual ~FileSystemManagerParent();
+
+ private:
+ RefPtr<fs::data::FileSystemDataManager> mDataManager;
+
+ FileSystemGetHandleResponse mRootResponse;
+
+ FlippedOnce<false> mRequestedAllowToClose;
+
+ DEBUGONLY(bool mRegistered = false);
+
+ DEBUGONLY(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..a7a4b13664
--- /dev/null
+++ b/dom/fs/parent/FileSystemManagerParentFactory.cpp
@@ -0,0 +1,114 @@
+/* -*- 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); });
+
+ QM_TRY(quota::QuotaManager::EnsureCreated(), IPC_OK(),
+ [aResolver](const auto rv) { aResolver(rv); });
+
+ auto* const quotaManager = quota::QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ QM_TRY_UNWRAP(auto principalMetadata,
+ quotaManager->GetInfoFromValidatedPrincipalInfo(aPrincipalInfo),
+ IPC_OK(), [aResolver](const auto rv) { aResolver(rv); });
+
+ quota::OriginMetadata originMetadata(std::move(principalMetadata),
+ 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->MutableIOTaskQueuePtr(), __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/FileSystemParentTypes.h b/dom/fs/parent/FileSystemParentTypes.h
new file mode 100644
index 0000000000..15cfe42bb0
--- /dev/null
+++ b/dom/fs/parent/FileSystemParentTypes.h
@@ -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/. */
+
+#ifndef DOM_FS_PARENT_FILESYSTEMPARENTTYPES_H_
+#define DOM_FS_PARENT_FILESYSTEMPARENTTYPES_H_
+
+#include "nsStringFwd.h"
+#include "nsTString.h"
+
+namespace mozilla::dom::fs {
+
+/**
+ * @brief FileId refers to a file on disk while EntryId refers to a path.
+ * Same user input path will always generate the same EntryId while the FileId
+ * can be different. Move methods can change the FileId which underlies
+ * an EntryId and multiple FileIds for temporary files can all map to the same
+ * EntryId.
+ */
+struct FileId {
+ explicit FileId(const nsCString& aValue) : mValue(aValue) {}
+
+ explicit FileId(nsCString&& aValue) : mValue(std::move(aValue)) {}
+
+ constexpr bool IsEmpty() const { return mValue.IsEmpty(); }
+
+ constexpr const nsCString& Value() const { return mValue; }
+
+ nsCString mValue;
+};
+
+inline bool operator==(const FileId& aLhs, const FileId& aRhs) {
+ return aLhs.mValue == aRhs.mValue;
+}
+
+inline bool operator!=(const FileId& aLhs, const FileId& aRhs) {
+ return aLhs.mValue != aRhs.mValue;
+}
+
+enum class FileMode { EXCLUSIVE, SHARED_FROM_EMPTY, SHARED_FROM_COPY };
+
+} // namespace mozilla::dom::fs
+
+#endif // DOM_FS_PARENT_FILESYSTEMPARENTTYPES_H_
diff --git a/dom/fs/parent/FileSystemQuotaClient.cpp b/dom/fs/parent/FileSystemQuotaClient.cpp
new file mode 100644
index 0000000000..fbe61b59df
--- /dev/null
+++ b/dom/fs/parent/FileSystemQuotaClient.cpp
@@ -0,0 +1,167 @@
+/* -*- 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 "datamodel/FileSystemDatabaseManager.h"
+#include "datamodel/FileSystemFileManager.h"
+#include "mozilla/dom/FileSystemDataManager.h"
+#include "mozilla/dom/quota/Assertions.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); };
+
+} // namespace
+
+FileSystemQuotaClient::FileSystemQuotaClient() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+}
+
+quota::Client::Type FileSystemQuotaClient::GetType() {
+ return quota::Client::Type::FILESYSTEM;
+}
+
+Result<quota::UsageInfo, nsresult> FileSystemQuotaClient::InitOrigin(
+ quota::PersistenceType aPersistenceType,
+ const quota::OriginMetadata& aOriginMetadata, const AtomicBool& aCanceled) {
+ quota::AssertIsOnIOThread();
+
+ {
+ QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& databaseFile,
+ data::GetDatabaseFile(aOriginMetadata).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,
+ data::GetStorageConnection(aOriginMetadata, /* aDirectoryLockId */ -1)
+ .mapErr(toNSResult));
+
+ QM_TRY(MOZ_TO_RESULT(
+ data::FileSystemDatabaseManager::RescanUsages(conn, aOriginMetadata)));
+
+ return data::FileSystemDatabaseManager::GetUsage(conn, aOriginMetadata)
+ .mapErr(toNSResult);
+}
+
+nsresult FileSystemQuotaClient::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> FileSystemQuotaClient::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 FileSystemQuotaClient::OnOriginClearCompleted(
+ quota::PersistenceType aPersistenceType, const nsACString& aOrigin) {
+ quota::AssertIsOnIOThread();
+}
+
+void FileSystemQuotaClient::OnRepositoryClearCompleted(
+ quota::PersistenceType aPersistenceType) {
+ quota::AssertIsOnIOThread();
+}
+
+void FileSystemQuotaClient::ReleaseIOThreadObjects() {
+ quota::AssertIsOnIOThread();
+}
+
+void FileSystemQuotaClient::AbortOperationsForLocks(
+ const DirectoryLockIdTable& aDirectoryLockIds) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ data::FileSystemDataManager::AbortOperationsForLocks(aDirectoryLockIds);
+}
+
+void FileSystemQuotaClient::AbortOperationsForProcess(
+ ContentParentId aContentParentId) {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+}
+
+void FileSystemQuotaClient::AbortAllOperations() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+}
+
+void FileSystemQuotaClient::StartIdleMaintenance() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+}
+
+void FileSystemQuotaClient::StopIdleMaintenance() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+}
+
+void FileSystemQuotaClient::InitiateShutdown() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ data::FileSystemDataManager::InitiateShutdown();
+}
+
+nsCString FileSystemQuotaClient::GetShutdownStatus() const {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ return "Not implemented"_ns;
+}
+
+bool FileSystemQuotaClient::IsShutdownCompleted() const {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ return data::FileSystemDataManager::IsShutdownCompleted();
+}
+
+void FileSystemQuotaClient::ForceKillActors() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ // Hopefully not needed.
+}
+
+void FileSystemQuotaClient::FinalizeShutdown() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ // Empty for now.
+}
+
+} // namespace mozilla::dom::fs
diff --git a/dom/fs/parent/FileSystemQuotaClient.h b/dom/fs/parent/FileSystemQuotaClient.h
new file mode 100644
index 0000000000..e0eced35b9
--- /dev/null
+++ b/dom/fs/parent/FileSystemQuotaClient.h
@@ -0,0 +1,69 @@
+/* -*- 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/dom/quota/Client.h"
+
+namespace mozilla::dom::fs {
+
+class FileSystemQuotaClient : public quota::Client {
+ public:
+ FileSystemQuotaClient();
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(mozilla::dom::fs::FileSystemQuotaClient,
+ 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 OnRepositoryClearCompleted(
+ quota::PersistenceType aPersistenceType) override;
+
+ void ReleaseIOThreadObjects() override;
+
+ void AbortOperationsForLocks(
+ const DirectoryLockIdTable& aDirectoryLockIds) override;
+
+ void AbortOperationsForProcess(ContentParentId aContentParentId) override;
+
+ void AbortAllOperations() override;
+
+ void StartIdleMaintenance() override;
+
+ void StopIdleMaintenance() override;
+
+ protected:
+ ~FileSystemQuotaClient() = default;
+
+ void InitiateShutdown() override;
+ bool IsShutdownCompleted() const override;
+ nsCString GetShutdownStatus() const override;
+ void ForceKillActors() override;
+ void FinalizeShutdown() override;
+};
+
+} // namespace mozilla::dom::fs
+
+#endif // DOM_FS_PARENT_FILESYSTEMQUOTACLIENT_H_
diff --git a/dom/fs/parent/FileSystemQuotaClientFactory.cpp b/dom/fs/parent/FileSystemQuotaClientFactory.cpp
new file mode 100644
index 0000000000..5d4897dfee
--- /dev/null
+++ b/dom/fs/parent/FileSystemQuotaClientFactory.cpp
@@ -0,0 +1,49 @@
+/* -*- 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 "FileSystemQuotaClientFactory.h"
+
+#include "FileSystemQuotaClient.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+namespace mozilla::dom::fs {
+
+namespace {
+
+StaticRefPtr<FileSystemQuotaClientFactory> gCustomFactory;
+
+} // namespace
+
+// static
+void FileSystemQuotaClientFactory::SetCustomFactory(
+ RefPtr<FileSystemQuotaClientFactory> aCustomFactory) {
+ gCustomFactory = std::move(aCustomFactory);
+}
+
+// static
+already_AddRefed<quota::Client>
+FileSystemQuotaClientFactory::CreateQuotaClient() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ if (gCustomFactory) {
+ return gCustomFactory->AllocQuotaClient();
+ }
+
+ auto factory = MakeRefPtr<FileSystemQuotaClientFactory>();
+
+ return factory->AllocQuotaClient();
+}
+
+already_AddRefed<quota::Client>
+FileSystemQuotaClientFactory::AllocQuotaClient() {
+ ::mozilla::ipc::AssertIsOnBackgroundThread();
+
+ RefPtr<FileSystemQuotaClient> result = new FileSystemQuotaClient();
+ return result.forget();
+}
+
+} // namespace mozilla::dom::fs
diff --git a/dom/fs/parent/FileSystemQuotaClientFactory.h b/dom/fs/parent/FileSystemQuotaClientFactory.h
new file mode 100644
index 0000000000..f71587fac0
--- /dev/null
+++ b/dom/fs/parent/FileSystemQuotaClientFactory.h
@@ -0,0 +1,49 @@
+/* -*- 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_FILESYSTEMQUOTACLIENTFACTORY_H_
+#define DOM_FS_PARENT_FILESYSTEMQUOTACLIENTFACTORY_H_
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "nsISupportsUtils.h"
+
+template <class>
+class RefPtr;
+
+namespace mozilla::dom {
+
+namespace quota {
+
+class Client;
+
+} // namespace quota
+
+namespace fs {
+
+class FileSystemQuotaClientFactory {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(
+ mozilla::dom::fs::FileSystemQuotaClientFactory);
+
+ static void SetCustomFactory(
+ RefPtr<FileSystemQuotaClientFactory> aCustomFactory);
+
+ static already_AddRefed<quota::Client> CreateQuotaClient();
+
+ protected:
+ virtual ~FileSystemQuotaClientFactory() = default;
+
+ virtual already_AddRefed<quota::Client> AllocQuotaClient();
+};
+
+inline already_AddRefed<quota::Client> CreateQuotaClient() {
+ return FileSystemQuotaClientFactory::CreateQuotaClient();
+}
+
+} // namespace fs
+} // namespace mozilla::dom
+
+#endif // DOM_FS_PARENT_FILESYSTEMQUOTACLIENTFACTORY_H_
diff --git a/dom/fs/parent/FileSystemStreamCallbacks.cpp b/dom/fs/parent/FileSystemStreamCallbacks.cpp
new file mode 100644
index 0000000000..fbe9d5f67b
--- /dev/null
+++ b/dom/fs/parent/FileSystemStreamCallbacks.cpp
@@ -0,0 +1,38 @@
+/* -*- 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"
+
+#include "mozilla/dom/quota/RemoteQuotaObjectParent.h"
+
+namespace mozilla::dom {
+
+FileSystemStreamCallbacks::FileSystemStreamCallbacks()
+ : mRemoteQuotaObjectParent(nullptr) {}
+
+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(!mRemoteQuotaObjectParent);
+
+ mRemoteQuotaObjectParent = aActor;
+}
+
+void FileSystemStreamCallbacks::UnregisterRemoteQuotaObjectParent(
+ NotNull<quota::RemoteQuotaObjectParent*> aActor) {
+ MOZ_ASSERT(mRemoteQuotaObjectParent);
+
+ mRemoteQuotaObjectParent = nullptr;
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/parent/FileSystemStreamCallbacks.h b/dom/fs/parent/FileSystemStreamCallbacks.h
new file mode 100644
index 0000000000..98e4713faf
--- /dev/null
+++ b/dom/fs/parent/FileSystemStreamCallbacks.h
@@ -0,0 +1,38 @@
+/* -*- 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"
+
+namespace mozilla::dom {
+
+class FileSystemStreamCallbacks : public nsIInterfaceRequestor,
+ public quota::RemoteQuotaObjectParentTracker {
+ public:
+ FileSystemStreamCallbacks();
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+
+ NS_DECL_NSIINTERFACEREQUESTOR
+
+ void RegisterRemoteQuotaObjectParent(
+ NotNull<quota::RemoteQuotaObjectParent*> aActor) override;
+
+ void UnregisterRemoteQuotaObjectParent(
+ NotNull<quota::RemoteQuotaObjectParent*> aActor) override;
+
+ protected:
+ virtual ~FileSystemStreamCallbacks() = default;
+
+ quota::RemoteQuotaObjectParent* mRemoteQuotaObjectParent;
+};
+
+} // 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..f0e9a3852c
--- /dev/null
+++ b/dom/fs/parent/FileSystemWritableFileStreamParent.cpp
@@ -0,0 +1,85 @@
+/* -*- 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 "FileSystemStreamCallbacks.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/FileSystemManagerParent.h"
+#include "mozilla/dom/quota/RemoteQuotaObjectParent.h"
+
+namespace mozilla::dom {
+
+class FileSystemWritableFileStreamParent::FileSystemWritableFileStreamCallbacks
+ : public FileSystemStreamCallbacks {
+ public:
+ void CloseRemoteQuotaObjectParent() {
+ if (mRemoteQuotaObjectParent) {
+ mRemoteQuotaObjectParent->Close();
+ }
+ }
+};
+
+FileSystemWritableFileStreamParent::FileSystemWritableFileStreamParent(
+ RefPtr<FileSystemManagerParent> aManager, const fs::EntryId& aEntryId,
+ const fs::FileId& aTemporaryFileId, bool aIsExclusive)
+ : mManager(std::move(aManager)),
+ mEntryId(aEntryId),
+ mTemporaryFileId(aTemporaryFileId),
+ mIsExclusive(aIsExclusive) {}
+
+FileSystemWritableFileStreamParent::~FileSystemWritableFileStreamParent() {
+ MOZ_ASSERT(mClosed);
+}
+
+mozilla::ipc::IPCResult FileSystemWritableFileStreamParent::RecvClose(
+ bool aAbort, CloseResolver&& aResolver) {
+ Close(aAbort);
+
+ aResolver(void_t());
+
+ return IPC_OK();
+}
+
+void FileSystemWritableFileStreamParent::ActorDestroy(ActorDestroyReason aWhy) {
+ if (mStreamCallbacks) {
+ mStreamCallbacks->CloseRemoteQuotaObjectParent();
+ mStreamCallbacks = nullptr;
+ }
+
+ if (!IsClosed()) {
+ Close(/* aAbort */ true);
+ }
+}
+
+nsIInterfaceRequestor*
+FileSystemWritableFileStreamParent::GetOrCreateStreamCallbacks() {
+ if (!mStreamCallbacks) {
+ if (mClosed) {
+ return nullptr;
+ }
+
+ mStreamCallbacks = MakeRefPtr<FileSystemWritableFileStreamCallbacks>();
+ }
+
+ return mStreamCallbacks.get();
+}
+
+void FileSystemWritableFileStreamParent::Close(bool aAbort) {
+ LOG(("Closing WritableFileStream"));
+
+ mClosed.Flip();
+
+ if (mIsExclusive) {
+ mManager->DataManagerStrongRef()->UnlockExclusive(mEntryId);
+ } else {
+ mManager->DataManagerStrongRef()->UnlockShared(mEntryId, mTemporaryFileId,
+ aAbort);
+ }
+}
+
+} // namespace mozilla::dom
diff --git a/dom/fs/parent/FileSystemWritableFileStreamParent.h b/dom/fs/parent/FileSystemWritableFileStreamParent.h
new file mode 100644
index 0000000000..bf24f5d146
--- /dev/null
+++ b/dom/fs/parent/FileSystemWritableFileStreamParent.h
@@ -0,0 +1,62 @@
+/* -*- 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/FileSystemParentTypes.h"
+#include "mozilla/dom/FileSystemTypes.h"
+#include "mozilla/dom/FlippedOnce.h"
+#include "mozilla/dom/PFileSystemWritableFileStreamParent.h"
+
+class nsIInterfaceRequestor;
+
+namespace mozilla::dom {
+
+class FileSystemManagerParent;
+
+class FileSystemWritableFileStreamParent
+ : public PFileSystemWritableFileStreamParent {
+ public:
+ FileSystemWritableFileStreamParent(RefPtr<FileSystemManagerParent> aManager,
+ const fs::EntryId& aEntryId,
+ const fs::FileId& aTemporaryFileId,
+ bool aIsExclusive);
+
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FileSystemWritableFileStreamParent,
+ override)
+
+ mozilla::ipc::IPCResult RecvClose(bool aAbort, CloseResolver&& aResolver);
+
+ void ActorDestroy(ActorDestroyReason aWhy) override;
+
+ nsIInterfaceRequestor* GetOrCreateStreamCallbacks();
+
+ private:
+ class FileSystemWritableFileStreamCallbacks;
+
+ virtual ~FileSystemWritableFileStreamParent();
+
+ bool IsClosed() const { return mClosed; }
+
+ void Close(bool aAbort);
+
+ const RefPtr<FileSystemManagerParent> mManager;
+
+ RefPtr<FileSystemWritableFileStreamCallbacks> mStreamCallbacks;
+
+ const fs::EntryId mEntryId;
+
+ const fs::FileId mTemporaryFileId;
+
+ const bool mIsExclusive;
+
+ 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..3e532f3ae4
--- /dev/null
+++ b/dom/fs/parent/ResultStatement.h
@@ -0,0 +1,173 @@
+/* -*- 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 "FileSystemParentTypes.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 BindFileIdByName(const nsACString& aField,
+ const FileId& aValue) {
+ return mStmt->BindUTF8StringAsBlobByName(aField, aValue.Value());
+ }
+
+ inline nsresult BindContentTypeByName(const nsACString& aField,
+ const ContentType& aValue) {
+ if (aValue.IsVoid()) {
+ return mStmt->BindNullByName(aField);
+ }
+
+ return mStmt->BindUTF8StringByName(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->GetUTF8String(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<FileId, QMResult> GetFileIdByColumn(Column aColumn) {
+ nsCString value;
+ QM_TRY(QM_TO_RESULT(mStmt->GetBlobAsUTF8String(aColumn, value)));
+
+ return FileId(std::move(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/StartedTransaction.cpp b/dom/fs/parent/StartedTransaction.cpp
new file mode 100644
index 0000000000..7fee51e61e
--- /dev/null
+++ b/dom/fs/parent/StartedTransaction.cpp
@@ -0,0 +1,35 @@
+/* -*- 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 "StartedTransaction.h"
+
+#include "ResultConnection.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+
+namespace mozilla::dom::fs {
+
+/* static */
+Result<StartedTransaction, QMResult> StartedTransaction::Create(
+ const ResultConnection& aConn) {
+ auto transaction = MakeUnique<mozStorageTransaction>(
+ aConn.get(), /* aCommitOnComplete */ false,
+ mozIStorageConnection::TRANSACTION_IMMEDIATE);
+
+ QM_TRY(QM_TO_RESULT(transaction->Start()));
+
+ return StartedTransaction(std::move(transaction));
+}
+
+nsresult StartedTransaction::Commit() { return mTransaction->Commit(); }
+
+nsresult StartedTransaction::Rollback() { return mTransaction->Rollback(); }
+
+StartedTransaction::StartedTransaction(
+ UniquePtr<mozStorageTransaction>&& aTransaction)
+ : mTransaction(std::move(aTransaction)) {}
+
+} // namespace mozilla::dom::fs
diff --git a/dom/fs/parent/StartedTransaction.h b/dom/fs/parent/StartedTransaction.h
new file mode 100644
index 0000000000..95a01326bb
--- /dev/null
+++ b/dom/fs/parent/StartedTransaction.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_STARTEDTRANSACTION_H_
+#define DOM_FS_PARENT_STARTEDTRANSACTION_H_
+
+#include "ResultConnection.h"
+#include "mozStorageHelper.h"
+#include "mozilla/dom/QMResult.h"
+
+namespace mozilla::dom::fs {
+
+class StartedTransaction {
+ public:
+ static Result<StartedTransaction, QMResult> Create(
+ const ResultConnection& aConn);
+
+ StartedTransaction(StartedTransaction&& aOther) = default;
+
+ StartedTransaction(const StartedTransaction& aOther) = delete;
+
+ nsresult Commit();
+
+ nsresult Rollback();
+
+ ~StartedTransaction() = default;
+
+ private:
+ explicit StartedTransaction(UniquePtr<mozStorageTransaction>&& aTransaction);
+
+ UniquePtr<mozStorageTransaction> mTransaction;
+};
+
+} // namespace mozilla::dom::fs
+
+#endif // DOM_FS_PARENT_STARTEDTRANSACTION_H_
diff --git a/dom/fs/parent/datamodel/FileSystemDataManager.cpp b/dom/fs/parent/datamodel/FileSystemDataManager.cpp
new file mode 100644
index 0000000000..549a8d5865
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemDataManager.cpp
@@ -0,0 +1,672 @@
+/* -*- 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 "ErrorList.h"
+#include "FileSystemDatabaseManager.h"
+#include "FileSystemDatabaseManagerVersion001.h"
+#include "FileSystemDatabaseManagerVersion002.h"
+#include "FileSystemFileManager.h"
+#include "FileSystemHashSource.h"
+#include "FileSystemParentTypes.h"
+#include "ResultStatement.h"
+#include "SchemaVersion001.h"
+#include "SchemaVersion002.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/QMResult.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 "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;
+ }
+}
+
+} // namespace
+
+Result<ResultConnection, QMResult> GetStorageConnection(
+ const quota::OriginMetadata& aOriginMetadata,
+ const int64_t aDirectoryLockId) {
+ MOZ_ASSERT(aDirectoryLockId >= -1);
+
+ // Ensure that storage is initialized and file system folder exists!
+ QM_TRY_INSPECT(const auto& dbFileUrl,
+ GetDatabaseFileURL(aOriginMetadata, 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;
+}
+
+Result<EntryId, QMResult> GetRootHandle(const Origin& origin) {
+ MOZ_ASSERT(!origin.IsEmpty());
+
+ return FileSystemHashSource::GenerateHash(origin, kRootString);
+}
+
+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,
+ RefPtr<quota::QuotaManager> aQuotaManager,
+ MovingNotNull<nsCOMPtr<nsIEventTarget>> aIOTarget,
+ MovingNotNull<RefPtr<TaskQueue>> aIOTaskQueue)
+ : mOriginMetadata(aOriginMetadata),
+ mQuotaManager(std::move(aQuotaManager)),
+ mBackgroundTarget(WrapNotNull(GetCurrentSerialEventTarget())),
+ mIOTarget(std::move(aIOTarget)),
+ mIOTaskQueue(std::move(aIOTaskQueue)),
+ mRegCount(0),
+ mVersion(0),
+ mState(State::Initial) {}
+
+FileSystemDataManager::~FileSystemDataManager() {
+ NS_ASSERT_OWNINGTHREAD(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& aValue) {
+ if (aValue.IsReject()) {
+ return CreatePromise::CreateAndReject(aValue.RejectValue(),
+ __func__);
+ }
+ 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__);
+ }
+
+ RefPtr<quota::QuotaManager> quotaManager = quota::QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ 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, std::move(quotaManager),
+ WrapMovingNotNull(streamTransportService),
+ WrapMovingNotNull(ioTaskQueue));
+
+ AddFileSystemDataManager(aOriginMetadata.mOrigin, dataManager);
+
+ return dataManager->BeginOpen()->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [dataManager = Registered<FileSystemDataManager>(dataManager)](
+ const BoolPromise::ResolveOrRejectValue& aValue) {
+ if (aValue.IsReject()) {
+ return CreatePromise::CreateAndReject(aValue.RejectValue(), __func__);
+ }
+
+ 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(&current)));
+ 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);
+
+#ifdef DEBUG
+ aActor->SetRegistered(true);
+#endif
+}
+
+void FileSystemDataManager::UnregisterActor(
+ NotNull<FileSystemManagerParent*> aActor) {
+ MOZ_ASSERT(mBackgroundThreadAccessible.Access()->mActors.Contains(aActor));
+
+ mBackgroundThreadAccessible.Access()->mActors.Remove(aActor);
+
+#ifdef DEBUG
+ aActor->SetRegistered(false);
+#endif
+
+ if (IsInactive()) {
+ BeginClose();
+ }
+}
+
+void FileSystemDataManager::RegisterAccessHandle(
+ NotNull<FileSystemAccessHandle*> aAccessHandle) {
+ MOZ_ASSERT(!mBackgroundThreadAccessible.Access()->mAccessHandles.Contains(
+ aAccessHandle));
+
+ mBackgroundThreadAccessible.Access()->mAccessHandles.Insert(aAccessHandle);
+}
+
+void FileSystemDataManager::UnregisterAccessHandle(
+ NotNull<FileSystemAccessHandle*> aAccessHandle) {
+ MOZ_ASSERT(mBackgroundThreadAccessible.Access()->mAccessHandles.Contains(
+ aAccessHandle));
+
+ mBackgroundThreadAccessible.Access()->mAccessHandles.Remove(aAccessHandle);
+
+ 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__);
+}
+
+// Note: Input can be temporary or main file id
+Result<bool, QMResult> FileSystemDataManager::IsLocked(
+ const FileId& aFileId) const {
+ auto checkIfEntryIdIsLocked = [this, &aFileId]() -> Result<bool, QMResult> {
+ QM_TRY_INSPECT(const EntryId& entryId,
+ mDatabaseManager->GetEntryId(aFileId));
+
+ return IsLocked(entryId);
+ };
+
+ auto valueToSome = [](auto aValue) { return Some(std::move(aValue)); };
+
+ QM_TRY_UNWRAP(Maybe<bool> maybeLocked,
+ QM_OR_ELSE_LOG_VERBOSE_IF(
+ // Expression.
+ (checkIfEntryIdIsLocked().map(valueToSome)),
+ // Predicate.
+ IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>,
+ // Fallback.
+ ([](const auto&) -> Result<Maybe<bool>, QMResult> {
+ return Some(false); // Non-existent files are not locked.
+ })));
+
+ if (!maybeLocked) {
+ // If the metadata is inaccessible, we block modifications.
+ return true;
+ }
+
+ return *maybeLocked;
+}
+
+Result<bool, QMResult> FileSystemDataManager::IsLocked(
+ const EntryId& aEntryId) const {
+ return mExclusiveLocks.Contains(aEntryId) || mSharedLocks.Contains(aEntryId);
+}
+
+Result<FileId, QMResult> FileSystemDataManager::LockExclusive(
+ const EntryId& aEntryId) {
+ QM_TRY_UNWRAP(const bool isLocked, IsLocked(aEntryId));
+ if (isLocked) {
+ return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ QM_TRY_INSPECT(const FileId& fileId,
+ mDatabaseManager->EnsureFileId(aEntryId));
+
+ // If the file has been removed, we should get a file not found error.
+ // Otherwise, if usage tracking cannot be started because file size is not
+ // known and attempts to read it are failing, lock is denied to freeze the
+ // quota usage until the (external) blocker is gone or the file is removed.
+ QM_TRY(QM_TO_RESULT(mDatabaseManager->BeginUsageTracking(fileId)));
+
+ LOG_VERBOSE(("ExclusiveLock"));
+ mExclusiveLocks.Insert(aEntryId);
+
+ return fileId;
+}
+
+// TODO: Improve reporting of failures, see bug 1840811.
+void FileSystemDataManager::UnlockExclusive(const EntryId& aEntryId) {
+ MOZ_ASSERT(mExclusiveLocks.Contains(aEntryId));
+
+ LOG_VERBOSE(("ExclusiveUnlock"));
+ mExclusiveLocks.Remove(aEntryId);
+
+ QM_TRY_INSPECT(const FileId& fileId, mDatabaseManager->GetFileId(aEntryId),
+ QM_VOID);
+
+ // On error, usage tracking remains on to prevent writes until usage is
+ // updated successfully.
+ QM_TRY(MOZ_TO_RESULT(mDatabaseManager->UpdateUsage(fileId)), QM_VOID);
+ QM_TRY(MOZ_TO_RESULT(mDatabaseManager->EndUsageTracking(fileId)), QM_VOID);
+}
+
+Result<FileId, QMResult> FileSystemDataManager::LockShared(
+ const EntryId& aEntryId) {
+ if (mExclusiveLocks.Contains(aEntryId)) {
+ return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ auto& count = mSharedLocks.LookupOrInsert(aEntryId);
+ if (!(1u + CheckedUint32(count)).isValid()) { // don't make the count invalid
+ return Err(QMResult(NS_ERROR_UNEXPECTED));
+ }
+
+ QM_TRY_INSPECT(const FileId& fileId,
+ mDatabaseManager->EnsureTemporaryFileId(aEntryId));
+
+ // If the file has been removed, we should get a file not found error.
+ // Otherwise, if usage tracking cannot be started because file size is not
+ // known and attempts to read it are failing, lock is denied to freeze the
+ // quota usage until the (external) blocker is gone or the file is removed.
+ QM_TRY(QM_TO_RESULT(mDatabaseManager->BeginUsageTracking(fileId)));
+
+ ++count;
+ LOG_VERBOSE(("SharedLock %u", count));
+
+ return fileId;
+}
+
+// TODO: Improve reporting of failures, see bug 1840811.
+void FileSystemDataManager::UnlockShared(const EntryId& aEntryId,
+ const FileId& aFileId, bool aAbort) {
+ MOZ_ASSERT(!mExclusiveLocks.Contains(aEntryId));
+ MOZ_ASSERT(mSharedLocks.Contains(aEntryId));
+
+ auto entry = mSharedLocks.Lookup(aEntryId);
+ MOZ_ASSERT(entry);
+
+ MOZ_ASSERT(entry.Data() > 0);
+ --entry.Data();
+
+ LOG_VERBOSE(("SharedUnlock %u", *entry));
+
+ if (0u == entry.Data()) {
+ entry.Remove();
+ }
+
+ // On error, usage tracking remains on to prevent writes until usage is
+ // updated successfully.
+ QM_TRY(MOZ_TO_RESULT(mDatabaseManager->UpdateUsage(aFileId)), QM_VOID);
+ QM_TRY(MOZ_TO_RESULT(mDatabaseManager->EndUsageTracking(aFileId)), QM_VOID);
+ QM_TRY(
+ MOZ_TO_RESULT(mDatabaseManager->MergeFileId(aEntryId, aFileId, aAbort)),
+ QM_VOID);
+}
+
+FileMode FileSystemDataManager::GetMode(bool aKeepData) const {
+ if (1 == mVersion) {
+ return FileMode::EXCLUSIVE;
+ }
+
+ return aKeepData ? FileMode::SHARED_FROM_COPY : FileMode::SHARED_FROM_EMPTY;
+}
+
+bool FileSystemDataManager::IsInactive() const {
+ auto data = mBackgroundThreadAccessible.Access();
+ return !mRegCount && !data->mActors.Count() && !data->mAccessHandles.Count();
+}
+
+void FileSystemDataManager::RequestAllowToClose() {
+ for (const auto& actor : mBackgroundThreadAccessible.Access()->mActors) {
+ actor->RequestAllowToClose();
+ }
+}
+
+RefPtr<BoolPromise> FileSystemDataManager::BeginOpen() {
+ MOZ_ASSERT(mQuotaManager);
+ MOZ_ASSERT(mState == State::Initial);
+
+ mState = State::Opening;
+
+ mQuotaManager
+ ->OpenClientDirectory(
+ {mOriginMetadata, mozilla::dom::quota::Client::FILESYSTEM})
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr<FileSystemDataManager>(this)](
+ quota::ClientDirectoryLockPromise::ResolveOrRejectValue&& value) {
+ if (value.IsReject()) {
+ return BoolPromise::CreateAndReject(value.RejectValue(),
+ __func__);
+ }
+
+ self->mDirectoryLock = std::move(value.ResolveValue());
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(mQuotaManager->IOThread(), __func__,
+ [self = RefPtr<FileSystemDataManager>(this)](
+ const BoolPromise::ResolveOrRejectValue& value) {
+ if (value.IsReject()) {
+ return BoolPromise::CreateAndReject(value.RejectValue(),
+ __func__);
+ }
+
+ QM_TRY(MOZ_TO_RESULT(
+ EnsureFileSystemDirectory(self->mOriginMetadata)),
+ CreateAndRejectBoolPromise);
+
+ return BoolPromise::CreateAndResolve(true, __func__);
+ })
+ ->Then(
+ MutableIOTaskQueuePtr(), __func__,
+ [self = RefPtr<FileSystemDataManager>(this)](
+ const BoolPromise::ResolveOrRejectValue& value) {
+ if (value.IsReject()) {
+ return BoolPromise::CreateAndReject(value.RejectValue(),
+ __func__);
+ }
+
+ QM_TRY_UNWRAP(auto connection,
+ GetStorageConnection(self->mOriginMetadata,
+ self->mDirectoryLock->Id()),
+ CreateAndRejectBoolPromiseFromQMResult);
+
+ QM_TRY_UNWRAP(UniquePtr<FileSystemFileManager> fmPtr,
+ FileSystemFileManager::CreateFileSystemFileManager(
+ self->mOriginMetadata),
+ CreateAndRejectBoolPromiseFromQMResult);
+
+ QM_TRY_UNWRAP(
+ self->mVersion,
+ QM_OR_ELSE_WARN_IF(
+ // Expression.
+ SchemaVersion002::InitializeConnection(
+ connection, *fmPtr, self->mOriginMetadata.mOrigin),
+ // Predicate.
+ ([](const auto&) { return true; }),
+ // Fallback.
+ ([&self, &connection](const auto&) {
+ QM_TRY_RETURN(SchemaVersion001::InitializeConnection(
+ connection, self->mOriginMetadata.mOrigin));
+ })),
+ CreateAndRejectBoolPromiseFromQMResult);
+
+ QM_TRY_UNWRAP(
+ EntryId rootId,
+ fs::data::GetRootHandle(self->mOriginMetadata.mOrigin),
+ CreateAndRejectBoolPromiseFromQMResult);
+
+ switch (self->mVersion) {
+ case 1: {
+ self->mDatabaseManager =
+ MakeUnique<FileSystemDatabaseManagerVersion001>(
+ self, std::move(connection), std::move(fmPtr), rootId);
+ break;
+ }
+
+ case 2: {
+ self->mDatabaseManager =
+ MakeUnique<FileSystemDatabaseManagerVersion002>(
+ self, std::move(connection), std::move(fmPtr), rootId);
+ break;
+ }
+
+ default:
+ break;
+ }
+
+ 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(MutableIOTaskQueuePtr(), __func__,
+ [self = RefPtr<FileSystemDataManager>(this)]() {
+ 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..ab0c603700
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemDataManager.h
@@ -0,0 +1,186 @@
+/* -*- 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 "FileSystemParentTypes.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 FileSystemAccessHandle;
+class FileSystemManagerParent;
+
+namespace fs {
+struct FileId;
+class FileSystemChildMetadata;
+} // namespace fs
+
+namespace quota {
+class DirectoryLock;
+class QuotaManager;
+} // namespace quota
+
+namespace fs::data {
+
+class FileSystemDatabaseManager;
+
+Result<EntryId, QMResult> GetRootHandle(const Origin& origin);
+
+Result<EntryId, QMResult> GetEntryHandle(
+ const FileSystemChildMetadata& aHandle);
+
+Result<ResultConnection, QMResult> GetStorageConnection(
+ const quota::OriginMetadata& aOriginMetadata,
+ const int64_t aDirectoryLockId);
+
+class FileSystemDataManager
+ : public SupportsCheckedUnsafePtr<CheckIf<DiagnosticAssertEnabled>> {
+ public:
+ enum struct State : uint8_t { Initial = 0, Opening, Open, Closing, Closed };
+
+ FileSystemDataManager(const quota::OriginMetadata& aOriginMetadata,
+ RefPtr<quota::QuotaManager> aQuotaManager,
+ 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_THREADSAFE_REFCOUNTING(FileSystemDataManager)
+
+ void AssertIsOnIOTarget() const;
+
+ const quota::OriginMetadata& OriginMetadataRef() const {
+ return mOriginMetadata;
+ }
+
+ nsISerialEventTarget* MutableBackgroundTargetPtr() const {
+ return mBackgroundTarget.get();
+ }
+
+ nsIEventTarget* MutableIOTargetPtr() const { return mIOTarget.get(); }
+
+ nsISerialEventTarget* MutableIOTaskQueuePtr() 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);
+
+ void RegisterAccessHandle(NotNull<FileSystemAccessHandle*> aAccessHandle);
+
+ void UnregisterAccessHandle(NotNull<FileSystemAccessHandle*> aAccessHandle);
+
+ bool IsOpen() const { return mState == State::Open; }
+
+ RefPtr<BoolPromise> OnOpen();
+
+ RefPtr<BoolPromise> OnClose();
+
+ Result<bool, QMResult> IsLocked(const FileId& aFileId) const;
+
+ Result<bool, QMResult> IsLocked(const EntryId& aEntryId) const;
+
+ Result<FileId, QMResult> LockExclusive(const EntryId& aEntryId);
+
+ void UnlockExclusive(const EntryId& aEntryId);
+
+ Result<FileId, QMResult> LockShared(const EntryId& aEntryId);
+
+ void UnlockShared(const EntryId& aEntryId, const FileId& aFileId,
+ bool aAbort);
+
+ FileMode GetMode(bool aKeepData) const;
+
+ 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;
+ nsTHashSet<FileSystemAccessHandle*> mAccessHandles;
+ };
+ ThreadBound<BackgroundThreadAccessible> mBackgroundThreadAccessible;
+
+ const quota::OriginMetadata mOriginMetadata;
+ nsTHashSet<EntryId> mExclusiveLocks;
+ nsTHashMap<EntryId, uint32_t> mSharedLocks;
+ NS_DECL_OWNINGEVENTTARGET
+ const RefPtr<quota::QuotaManager> mQuotaManager;
+ 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;
+ DatabaseVersion mVersion;
+ 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..2b76a3b09d
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemDatabaseManager.cpp
@@ -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/. */
+
+#include "FileSystemDatabaseManager.h"
+
+#include "FileSystemDatabaseManagerVersion001.h"
+#include "FileSystemDatabaseManagerVersion002.h"
+#include "FileSystemFileManager.h"
+#include "ResultConnection.h"
+#include "ResultStatement.h"
+#include "SchemaVersion001.h"
+#include "SchemaVersion002.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+
+namespace mozilla::dom::fs::data {
+
+namespace {
+
+Result<Usage, QMResult> GetFileUsage(const ResultConnection& aConnection) {
+ DatabaseVersion version = 0;
+ QM_TRY(QM_TO_RESULT(aConnection->GetSchemaVersion(&version)));
+
+ switch (version) {
+ case 0: {
+ return 0;
+ }
+
+ case 1: {
+ QM_TRY_RETURN(
+ FileSystemDatabaseManagerVersion001::GetFileUsage(aConnection));
+ }
+
+ case 2: {
+ QM_TRY_RETURN(
+ FileSystemDatabaseManagerVersion002::GetFileUsage(aConnection));
+ }
+
+ default:
+ break;
+ }
+
+ return Err(QMResult(NS_ERROR_NOT_IMPLEMENTED));
+}
+
+} // namespace
+
+/* static */
+nsresult FileSystemDatabaseManager::RescanUsages(
+ const ResultConnection& aConnection,
+ const quota::OriginMetadata& aOriginMetadata) {
+ DatabaseVersion version = 0;
+ QM_TRY(MOZ_TO_RESULT(aConnection->GetSchemaVersion(&version)));
+
+ switch (version) {
+ case 0: {
+ return NS_OK;
+ }
+
+ case 1:
+ return FileSystemDatabaseManagerVersion001::RescanTrackedUsages(
+ aConnection, aOriginMetadata);
+
+ case 2: {
+ return FileSystemDatabaseManagerVersion002::RescanTrackedUsages(
+ aConnection, aOriginMetadata);
+ }
+
+ default:
+ break;
+ }
+
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+/* static */
+Result<quota::UsageInfo, QMResult> FileSystemDatabaseManager::GetUsage(
+ const ResultConnection& aConnection,
+ const quota::OriginMetadata& aOriginMetadata) {
+ QM_TRY_INSPECT(const auto& databaseFile, GetDatabaseFile(aOriginMetadata));
+
+ // 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)));
+
+ QM_TRY_INSPECT(const Usage& fileUsage, GetFileUsage(aConnection));
+
+ // XXX: DatabaseUsage is currently total usage for most forms of storage
+ result += quota::DatabaseUsageType(Some(fileUsage));
+
+ return result;
+}
+
+} // 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..b7a4e352fe
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemDatabaseManager.h
@@ -0,0 +1,229 @@
+/* -*- 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 {
+
+namespace quota {
+
+struct OriginMetadata;
+
+} // namespace quota
+
+namespace fs {
+
+struct FileId;
+enum class FileMode;
+class FileSystemChildMetadata;
+class FileSystemEntryMetadata;
+class FileSystemDirectoryListing;
+class FileSystemEntryPair;
+
+namespace data {
+
+using FileSystemConnection = fs::ResultConnection;
+
+class FileSystemDatabaseManager {
+ public:
+ /**
+ * @brief Updates stored usage data for all tracked files.
+ *
+ * @return nsresult error code
+ */
+ static nsresult RescanUsages(const ResultConnection& aConnection,
+ const quota::OriginMetadata& aOriginMetadata);
+
+ /**
+ * @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 quota::OriginMetadata& aOriginMetadata);
+
+ /**
+ * @brief Refreshes the stored file size.
+ *
+ * @param aEntry EntryId of the file whose size is refreshed.
+ */
+ virtual nsresult UpdateUsage(const FileId& aFileId) = 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
+ * @param aType Content type which is ignored if the file already exists
+ * @param aCreate true if file is to be created when it does not already exist
+ * @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, const FileId& aFileId,
+ const FileMode& aMode, ContentType& 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<EntryId, QMResult> The relevant entry id or error
+ */
+ virtual Result<EntryId, 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<EntryId, QMResult> The relevant entry id or error
+ */
+ virtual Result<EntryId, 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 Generates an EntryId for a given parent EntryId and filename.
+ */
+ virtual Result<EntryId, QMResult> GetEntryId(
+ const FileSystemChildMetadata& aHandle) const = 0;
+
+ /**
+ * @brief To check if a file under a directory is locked, we need to map
+ * fileId's to entries.
+ *
+ * @param aFileId a FileId
+ * @return Result<EntryId, QMResult> Entry id of a temporary or main file
+ */
+ virtual Result<EntryId, QMResult> GetEntryId(const FileId& aFileId) const = 0;
+
+ /**
+ * @brief Make sure EntryId maps to a FileId. This method should be called
+ * before exclusive locking is attempted.
+ */
+ virtual Result<FileId, QMResult> EnsureFileId(const EntryId& aEntryId) = 0;
+
+ /**
+ * @brief Make sure EntryId maps to a temporary FileId. This method should be
+ * called before shared locking is attempted.
+ */
+ virtual Result<FileId, QMResult> EnsureTemporaryFileId(
+ const EntryId& aEntryId) = 0;
+
+ /**
+ * @brief To support moves in metadata, the actual files on disk are tagged
+ * with file id's which are mapped to entry id's which represent paths.
+ * This function returns the main file corresponding to an entry.
+ *
+ * @param aEntryId An id of an entry
+ * @return Result<EntryId, QMResult> Main file id, used by exclusive locks
+ */
+ virtual Result<FileId, QMResult> GetFileId(const EntryId& aEntryId) const = 0;
+
+ /**
+ * @brief Flag aFileId as the main file for aEntryId or abort. Removes the
+ * file which did not get flagged as the main file.
+ */
+ virtual nsresult MergeFileId(const EntryId& aEntryId, const FileId& aFileId,
+ bool aAbort) = 0;
+
+ /**
+ * @brief Close database connection.
+ */
+ virtual void Close() = 0;
+
+ /**
+ * @brief Start tracking file's usage.
+ */
+ virtual nsresult BeginUsageTracking(const FileId& aFileId) = 0;
+
+ /**
+ * @brief Stop tracking file's usage.
+ */
+ virtual nsresult EndUsageTracking(const FileId& aFileId) = 0;
+
+ virtual ~FileSystemDatabaseManager() = default;
+};
+
+} // namespace data
+} // namespace fs
+} // namespace dom
+} // 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..c65bf01508
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.cpp
@@ -0,0 +1,1567 @@
+/* -*- 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 "ErrorList.h"
+#include "FileSystemContentTypeGuess.h"
+#include "FileSystemDataManager.h"
+#include "FileSystemFileManager.h"
+#include "FileSystemParentTypes.h"
+#include "ResultStatement.h"
+#include "StartedTransaction.h"
+#include "mozStorageHelper.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/FileSystemDataManager.h"
+#include "mozilla/dom/FileSystemHandle.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/FileSystemTypes.h"
+#include "mozilla/dom/PFileSystemManager.h"
+#include "mozilla/dom/quota/Client.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/quota/QuotaObject.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsString.h"
+
+namespace mozilla::dom {
+
+using FileSystemEntries = nsTArray<fs::FileSystemEntryMetadata>;
+
+namespace fs::data {
+
+namespace {
+
+constexpr const nsLiteralCString gDescendantsQuery =
+ "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;
+
+Result<bool, QMResult> IsDirectoryEmpty(const 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 INNER 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<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 INNER JOIN Entries USING (handle) "
+ "WHERE Files.name = :name AND Entries.parent = :parent ) "
+ ";"_ns;
+
+ QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aHandle));
+}
+
+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));
+}
+
+nsresult PerformRename(const FileSystemConnection& aConnection,
+ const FileSystemEntryMetadata& aHandle,
+ const Name& aNewName, const ContentType& aNewType,
+ 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;
+ }
+
+ // 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));
+ if (!aNewType.IsVoid()) {
+ QM_TRY(MOZ_TO_RESULT(stmt.BindContentTypeByName("type"_ns, aNewType)));
+ }
+ 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, VoidCString(),
+ updateDirectoryNameQuery);
+}
+
+nsresult PerformRenameFile(const FileSystemConnection& aConnection,
+ const FileSystemEntryMetadata& aHandle,
+ const Name& aNewName, const ContentType& aNewType) {
+ const nsLiteralCString updateFileTypeAndNameQuery =
+ "UPDATE Files SET type = :type, name = :name "
+ "WHERE handle = :handle ;"_ns;
+
+ const nsLiteralCString updateFileNameQuery =
+ "UPDATE Files SET name = :name WHERE handle = :handle ;"_ns;
+
+ if (aNewType.IsVoid()) {
+ return PerformRename(aConnection, aHandle, aNewName, aNewType,
+ updateFileNameQuery);
+ }
+
+ return PerformRename(aConnection, aHandle, aNewName, aNewType,
+ updateFileTypeAndNameQuery);
+}
+
+template <class HandlerType>
+nsresult SetUsageTrackingImpl(const FileSystemConnection& aConnection,
+ const FileId& aFileId, bool aTracked,
+ HandlerType&& aOnMissingFile) {
+ const nsLiteralCString setTrackedQuery =
+ "INSERT INTO Usages "
+ "( handle, tracked ) "
+ "VALUES "
+ "( :handle, :tracked ) "
+ "ON CONFLICT(handle) DO "
+ "UPDATE SET tracked = excluded.tracked "
+ ";"_ns;
+
+ const nsresult customReturnValue =
+ aTracked ? NS_ERROR_DOM_NOT_FOUND_ERR : NS_OK;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, setTrackedQuery));
+ QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
+ QM_TRY(MOZ_TO_RESULT(stmt.BindBooleanByName("tracked"_ns, aTracked)));
+ QM_TRY(MOZ_TO_RESULT(stmt.Execute()), customReturnValue,
+ std::forward<HandlerType>(aOnMissingFile));
+
+ return NS_OK;
+}
+
+Result<nsTArray<FileId>, QMResult> GetTrackedFiles(
+ const FileSystemConnection& aConnection) {
+ // The same query works for both 001 and 002 schemas because handle is
+ // an entry id and later on a file id, respectively.
+ static const nsLiteralCString getTrackedFilesQuery =
+ "SELECT handle FROM Usages WHERE tracked = TRUE;"_ns;
+ nsTArray<FileId> trackedFiles;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, getTrackedFilesQuery));
+ QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
+
+ while (moreResults) {
+ QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 0u));
+
+ trackedFiles.AppendElement(fileId); // TODO: fallible?
+
+ QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
+ }
+
+ return trackedFiles;
+}
+
+/** This handles the file not found error by assigning 0 usage to the dangling
+ * handle and puts the handle to a non-tracked state. Otherwise, when the
+ * file or database cannot be reached, the file remains in the tracked state.
+ */
+template <class QuotaCacheUpdate>
+nsresult UpdateUsageForFileEntry(const FileSystemConnection& aConnection,
+ const FileSystemFileManager& aFileManager,
+ const FileId& aFileId,
+ const nsLiteralCString& aUpdateQuery,
+ QuotaCacheUpdate&& aUpdateCache) {
+ QM_TRY_INSPECT(const auto& fileHandle, aFileManager.GetFile(aFileId));
+
+ // A file could have changed in a way which doesn't allow to read its size.
+ QM_TRY_UNWRAP(
+ const Usage fileSize,
+ QM_OR_ELSE_WARN_IF(
+ // Expression.
+ MOZ_TO_RESULT_INVOKE_MEMBER(fileHandle, GetFileSize),
+ // Predicate.
+ ([](const nsresult rv) { return rv == NS_ERROR_FILE_NOT_FOUND; }),
+ // Fallback. If the file does no longer exist, treat it as 0-sized.
+ ErrToDefaultOk<Usage>));
+
+ QM_TRY(MOZ_TO_RESULT(aUpdateCache(fileSize)));
+
+ // No transaction as one statement succeeds or fails atomically
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, aUpdateQuery));
+
+ QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
+
+ QM_TRY(MOZ_TO_RESULT(stmt.BindUsageByName("usage"_ns, fileSize)));
+
+ QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
+
+ return NS_OK;
+}
+
+nsresult UpdateUsageUnsetTracked(const FileSystemConnection& aConnection,
+ const FileSystemFileManager& aFileManager,
+ const FileId& aFileId) {
+ static const nsLiteralCString updateUsagesUnsetTrackedQuery =
+ "UPDATE Usages SET usage = :usage, tracked = FALSE "
+ "WHERE handle = :handle;"_ns;
+
+ auto noCacheUpdateNeeded = [](auto) { return NS_OK; };
+
+ return UpdateUsageForFileEntry(aConnection, aFileManager, aFileId,
+ updateUsagesUnsetTrackedQuery,
+ std::move(noCacheUpdateNeeded));
+}
+
+/**
+ * @brief Get the recorded usage only if the file is in tracked state.
+ * During origin initialization, if the usage on disk is unreadable, the latest
+ * recorded usage is reported to the quota manager for the tracked files.
+ * To allow writing, we attempt to update the real usage with one database and
+ * one file size query.
+ */
+Result<Maybe<Usage>, QMResult> GetMaybeTrackedUsage(
+ const FileSystemConnection& aConnection, const FileId& aFileId) {
+ const nsLiteralCString trackedUsageQuery =
+ "SELECT usage FROM Usages WHERE tracked = TRUE AND handle = :handle "
+ ");"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, trackedUsageQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
+
+ QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
+ if (!moreResults) {
+ return Maybe<Usage>(Nothing());
+ }
+
+ QM_TRY_UNWRAP(Usage trackedUsage, stmt.GetUsageByColumn(/* Column */ 0u));
+
+ return Some(trackedUsage);
+}
+
+Result<bool, nsresult> ScanTrackedFiles(
+ const FileSystemConnection& aConnection,
+ const FileSystemFileManager& aFileManager) {
+ QM_TRY_INSPECT(const nsTArray<FileId>& trackedFiles,
+ GetTrackedFiles(aConnection).mapErr(toNSResult));
+
+ bool ok = true;
+ for (const auto& fileId : trackedFiles) {
+ // On success, tracked is set to false, otherwise its value is kept (= true)
+ QM_WARNONLY_TRY(MOZ_TO_RESULT(UpdateUsageUnsetTracked(
+ aConnection, aFileManager, fileId)),
+ [&ok](const auto& /*aRv*/) { ok = false; });
+ }
+
+ return ok;
+}
+
+Result<Ok, QMResult> DeleteEntry(const FileSystemConnection& aConnection,
+ const EntryId& aEntryId) {
+ // If it's a directory, deleting the handle will cascade
+ const nsLiteralCString deleteEntryQuery =
+ "DELETE FROM Entries "
+ "WHERE handle = :handle "
+ ";"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, deleteEntryQuery));
+
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+
+ return Ok{};
+}
+
+Result<int32_t, QMResult> GetTrackedFilesCount(
+ const FileSystemConnection& aConnection) {
+ // TODO: We could query the count directly
+ QM_TRY_INSPECT(const auto& trackedFiles, GetTrackedFiles(aConnection));
+
+ CheckedInt32 checkedFileCount = trackedFiles.Length();
+ QM_TRY(OkIf(checkedFileCount.isValid()),
+ Err(QMResult(NS_ERROR_ILLEGAL_VALUE)));
+
+ return checkedFileCount.value();
+}
+
+void LogWithFilename(const FileSystemFileManager& aFileManager,
+ const char* aFormat, const FileId& aFileId) {
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ QM_TRY_INSPECT(const auto& localFile, aFileManager.GetFile(aFileId), QM_VOID);
+
+ nsAutoString localPath;
+ QM_TRY(MOZ_TO_RESULT(localFile->GetPath(localPath)), QM_VOID);
+ LOG((aFormat, NS_ConvertUTF16toUTF8(localPath).get()));
+}
+
+Result<bool, QMResult> IsAnyDescendantLocked(
+ const FileSystemConnection& aConnection,
+ const FileSystemDataManager& aDataManager, const EntryId& aEntryId) {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, gDescendantsQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
+
+ while (moreResults) {
+ // Works only for version 001
+ QM_TRY_INSPECT(const EntryId& entryId,
+ stmt.GetEntryIdByColumn(/* Column */ 0u));
+
+ QM_TRY_UNWRAP(const bool isLocked, aDataManager.IsLocked(entryId), true);
+ if (isLocked) {
+ return true;
+ }
+
+ QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
+ }
+
+ return false;
+}
+
+} // namespace
+
+FileSystemDatabaseManagerVersion001::FileSystemDatabaseManagerVersion001(
+ FileSystemDataManager* aDataManager, FileSystemConnection&& aConnection,
+ UniquePtr<FileSystemFileManager>&& aFileManager, const EntryId& aRootEntry)
+ : mDataManager(aDataManager),
+ mConnection(aConnection),
+ mFileManager(std::move(aFileManager)),
+ mRootEntry(aRootEntry),
+ mClientMetadata(aDataManager->OriginMetadataRef(),
+ quota::Client::FILESYSTEM),
+ mFilesOfUnknownUsage(-1) {}
+
+/* static */
+nsresult FileSystemDatabaseManagerVersion001::RescanTrackedUsages(
+ const FileSystemConnection& aConnection,
+ const quota::OriginMetadata& aOriginMetadata) {
+ QM_TRY_UNWRAP(UniquePtr<FileSystemFileManager> fileManager,
+ data::FileSystemFileManager::CreateFileSystemFileManager(
+ aOriginMetadata));
+
+ QM_TRY_UNWRAP(bool ok, ScanTrackedFiles(aConnection, *fileManager));
+ if (ok) {
+ return NS_OK;
+ }
+
+ // Retry once without explicit delay
+ QM_TRY_UNWRAP(ok, ScanTrackedFiles(aConnection, *fileManager));
+ if (!ok) {
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+/* static */
+Result<Usage, QMResult> FileSystemDatabaseManagerVersion001::GetFileUsage(
+ const FileSystemConnection& aConnection) {
+ 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::UpdateUsage(
+ const FileId& aFileId) {
+ // We don't track directories or non-existent files.
+ QM_TRY_UNWRAP(bool fileExists, DoesFileIdExist(aFileId).mapErr(toNSResult));
+ if (!fileExists) {
+ return NS_OK; // May be deleted before update, no assert
+ }
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> file, mFileManager->GetFile(aFileId));
+ MOZ_ASSERT(file);
+
+ Usage fileSize = 0;
+ bool exists = false;
+ QM_TRY(MOZ_TO_RESULT(file->Exists(&exists)));
+ if (exists) {
+ QM_TRY(MOZ_TO_RESULT(file->GetFileSize(&fileSize)));
+ }
+
+ QM_TRY(MOZ_TO_RESULT(UpdateUsageInDatabase(aFileId, fileSize)));
+
+ 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_INSPECT(const EntryId& entryId, GetEntryId(aHandle));
+ MOZ_ASSERT(!entryId.IsEmpty());
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
+
+ {
+ 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
+ QM_TRY(OkIf(!exists), Err(QMResult(NS_ERROR_DOM_TYPE_MISMATCH_ERR)));
+
+ QM_TRY_UNWRAP(exists, DoesFileExist(mConnection, aHandle));
+
+ if (exists) {
+ QM_TRY_RETURN(FindEntryId(mConnection, aHandle, /* aIsFile */ 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, type, name ) "
+ "VALUES "
+ "( :handle, :type, :name ) "
+ ";"_ns;
+
+ QM_TRY_INSPECT(const EntryId& entryId, GetEntryId(aHandle));
+ MOZ_ASSERT(!entryId.IsEmpty());
+
+ const ContentType type = DetermineContentType(name);
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
+
+ {
+ 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_PROPAGATE,
+ ([this, &aHandle](const auto& aRv) {
+ QM_TRY_UNWRAP(bool parentExists,
+ DoesDirectoryExist(mConnection, aHandle.parentId()),
+ QM_VOID);
+ QM_TRY(OkIf(parentExists), QM_VOID);
+ }));
+ }
+
+ {
+ 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.BindContentTypeByName("type"_ns, type)));
+ 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;
+}
+
+nsresult FileSystemDatabaseManagerVersion001::GetFile(
+ const EntryId& aEntryId, const FileId& aFileId, const FileMode& aMode,
+ ContentType& aType, TimeStamp& lastModifiedMilliSeconds,
+ nsTArray<Name>& aPath, nsCOMPtr<nsIFile>& aFile) const {
+ MOZ_ASSERT(!aFileId.IsEmpty());
+ MOZ_ASSERT(aMode == FileMode::EXCLUSIVE);
+
+ const FileSystemEntryPair endPoints(mRootEntry, aEntryId);
+ QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, endPoints));
+ if (aPath.IsEmpty()) {
+ return NS_ERROR_DOM_NOT_FOUND_ERR;
+ }
+
+ QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, aEntryId, aType)));
+ QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId));
+
+ PRTime lastModTime = 0;
+ QM_TRY(MOZ_TO_RESULT(aFile->GetLastModifiedTime(&lastModTime)));
+ lastModifiedMilliSeconds = static_cast<TimeStamp>(lastModTime);
+
+ aPath.Reverse();
+
+ return NS_OK;
+}
+
+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;
+}
+
+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));
+
+ MOZ_ASSERT(mDataManager);
+ QM_TRY_UNWRAP(const bool isLocked,
+ IsAnyDescendantLocked(mConnection, *mDataManager, entryId));
+
+ QM_TRY(OkIf(!isLocked),
+ Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR)));
+
+ if (!aRecursive && !isEmpty) {
+ return Err(QMResult(NS_ERROR_DOM_INVALID_MODIFICATION_ERR));
+ }
+
+ QM_TRY_UNWRAP(Usage usage, GetUsagesOfDescendants(entryId));
+
+ QM_TRY_INSPECT(const nsTArray<FileId>& descendants,
+ FindFilesUnderEntry(entryId));
+
+ nsTArray<FileId> failedRemovals;
+ QM_TRY_UNWRAP(DebugOnly<Usage> removedUsage,
+ mFileManager->RemoveFiles(descendants, failedRemovals));
+
+ // Usage is for the current main file but we remove temporary files too.
+ MOZ_ASSERT_IF(failedRemovals.IsEmpty() && (0 == mFilesOfUnknownUsage),
+ usage <= removedUsage);
+
+ TryRemoveDuringIdleMaintenance(failedRemovals);
+
+ auto isInFailedRemovals = [&failedRemovals](const auto& aFileId) {
+ return failedRemovals.cend() !=
+ std::find_if(failedRemovals.cbegin(), failedRemovals.cend(),
+ [&aFileId](const auto& aFailedRemoval) {
+ return aFileId == aFailedRemoval;
+ });
+ };
+
+ for (const auto& fileId : descendants) {
+ if (!isInFailedRemovals(fileId)) {
+ QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(fileId)));
+ }
+ }
+
+ if (usage > 0) { // Performance!
+ DecreaseCachedQuotaUsage(usage);
+ }
+
+ QM_TRY(DeleteEntry(mConnection, entryId));
+
+ 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
+ QM_TRY_UNWRAP(const bool isLocked, mDataManager->IsLocked(entryId));
+ if (isLocked) {
+ LOG(("Trying to remove in-use file"));
+ return Err(QMResult(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR));
+ }
+
+ QM_TRY_INSPECT(const nsTArray<FileId>& diskItems,
+ FindFilesUnderEntry(entryId));
+
+ QM_TRY_UNWRAP(Usage usage, GetUsagesOfDescendants(entryId));
+
+ nsTArray<FileId> failedRemovals;
+ QM_TRY_UNWRAP(DebugOnly<Usage> removedUsage,
+ mFileManager->RemoveFiles(diskItems, failedRemovals));
+
+ // We only check the most common case. This can fail spuriously if an external
+ // application writes to the file, or OS reports zero size due to corruption.
+ MOZ_ASSERT_IF(failedRemovals.IsEmpty() && (0 == mFilesOfUnknownUsage),
+ usage == removedUsage);
+
+ TryRemoveDuringIdleMaintenance(failedRemovals);
+
+ auto isInFailedRemovals = [&failedRemovals](const auto& aFileId) {
+ return failedRemovals.cend() !=
+ std::find_if(failedRemovals.cbegin(), failedRemovals.cend(),
+ [&aFileId](const auto& aFailedRemoval) {
+ return aFileId == aFailedRemoval;
+ });
+ };
+
+ for (const auto& fileId : diskItems) {
+ if (!isInFailedRemovals(fileId)) {
+ QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(fileId)));
+ }
+ }
+
+ if (usage > 0) { // Performance!
+ DecreaseCachedQuotaUsage(usage);
+ }
+
+ QM_TRY(DeleteEntry(mConnection, entryId));
+
+ return true;
+}
+
+Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::RenameEntry(
+ const FileSystemEntryMetadata& aHandle, const Name& aNewName) {
+ const auto& entryId = aHandle.entryId();
+
+ // Can't rename root
+ if (mRootEntry == entryId) {
+ return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
+ }
+
+ // Verify the source exists
+ QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId),
+ Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)));
+
+ // Are we actually renaming?
+ if (aHandle.entryName() == aNewName) {
+ return entryId;
+ }
+
+ QM_TRY(QM_TO_RESULT(PrepareRenameEntry(mConnection, mDataManager, aHandle,
+ aNewName, isFile)));
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
+
+ if (isFile) {
+ const ContentType type = DetermineContentType(aNewName);
+ QM_TRY(
+ QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, aNewName, type)));
+ } else {
+ QM_TRY(
+ QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, aNewName)));
+ }
+
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+
+ return entryId;
+}
+
+Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::MoveEntry(
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewDesignation) {
+ const auto& entryId = aHandle.entryId();
+ MOZ_ASSERT(!entryId.IsEmpty());
+
+ if (mRootEntry == entryId) {
+ return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
+ }
+
+ // Verify the source exists
+ QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId),
+ Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)));
+
+ // If the rename doesn't change the name or directory, just return success.
+ // XXX Needs to be added to the spec
+ QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeSame,
+ IsSame(mConnection, aHandle, aNewDesignation, isFile));
+ if (maybeSame && maybeSame.value()) {
+ return entryId;
+ }
+
+ QM_TRY(QM_TO_RESULT(PrepareMoveEntry(mConnection, mDataManager, aHandle,
+ aNewDesignation, isFile)));
+
+ const nsLiteralCString updateEntryParentQuery =
+ "UPDATE Entries "
+ "SET parent = :parent "
+ "WHERE handle = :handle "
+ ";"_ns;
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
+
+ {
+ // 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()));
+ }
+
+ const Name& newName = aNewDesignation.childName();
+
+ // Are we actually renaming?
+ if (aHandle.entryName() == newName) {
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+
+ return entryId;
+ }
+
+ if (isFile) {
+ const ContentType type = DetermineContentType(newName);
+ QM_TRY(
+ QM_TO_RESULT(PerformRenameFile(mConnection, aHandle, newName, type)));
+ } else {
+ QM_TRY(QM_TO_RESULT(PerformRenameDirectory(mConnection, aHandle, newName)));
+ }
+
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+
+ return entryId;
+}
+
+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;
+}
+
+Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetEntryId(
+ const FileSystemChildMetadata& aHandle) const {
+ return GetUniqueEntryId(mConnection, aHandle);
+}
+
+Result<EntryId, QMResult> FileSystemDatabaseManagerVersion001::GetEntryId(
+ const FileId& aFileId) const {
+ return aFileId.Value();
+}
+
+Result<FileId, QMResult> FileSystemDatabaseManagerVersion001::EnsureFileId(
+ const EntryId& aEntryId) {
+ return FileId(aEntryId);
+}
+
+Result<FileId, QMResult>
+FileSystemDatabaseManagerVersion001::EnsureTemporaryFileId(
+ const EntryId& aEntryId) {
+ return FileId(aEntryId);
+}
+
+Result<FileId, QMResult> FileSystemDatabaseManagerVersion001::GetFileId(
+ const EntryId& aEntryId) const {
+ return FileId(aEntryId);
+}
+
+nsresult FileSystemDatabaseManagerVersion001::MergeFileId(
+ const EntryId& /* aEntryId */, const FileId& /* aFileId */,
+ bool /* aAbort */) {
+ // Version 001 should always use exclusive mode and not get here.
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+void FileSystemDatabaseManagerVersion001::Close() { mConnection->Close(); }
+
+nsresult FileSystemDatabaseManagerVersion001::BeginUsageTracking(
+ const FileId& aFileId) {
+ MOZ_ASSERT(!aFileId.IsEmpty());
+
+ // If file is already tracked but we cannot read its size, error.
+ // If file does not exist, this will succeed because usage is zero.
+ QM_TRY(EnsureUsageIsKnown(aFileId));
+
+ // If file does not exist, set usage tracking to true fails with
+ // file not found error.
+ QM_TRY(MOZ_TO_RESULT(SetUsageTracking(aFileId, true)));
+
+ return NS_OK;
+}
+
+nsresult FileSystemDatabaseManagerVersion001::EndUsageTracking(
+ const FileId& aFileId) {
+ // This is expected to fail only if database is unreachable.
+ QM_TRY(MOZ_TO_RESULT(SetUsageTracking(aFileId, false)));
+
+ return NS_OK;
+}
+
+Result<bool, QMResult> FileSystemDatabaseManagerVersion001::DoesFileIdExist(
+ const FileId& aFileId) const {
+ MOZ_ASSERT(!aFileId.IsEmpty());
+
+ QM_TRY_RETURN(DoesFileExist(mConnection, aFileId.Value()));
+}
+
+nsresult FileSystemDatabaseManagerVersion001::RemoveFileId(
+ const FileId& /* aFileId */) {
+ return NS_OK;
+}
+
+/**
+ * @brief Get the sum of usages for all file descendants of a directory entry.
+ * We obtain the value with one query, which is presumably better than having a
+ * separate query for each individual descendant.
+ * TODO: Check if this is true
+ *
+ * Please see GetFileUsage documentation for why we use the latest recorded
+ * value from the database instead of the file size property from the disk.
+ */
+Result<Usage, QMResult>
+FileSystemDatabaseManagerVersion001::GetUsagesOfDescendants(
+ const EntryId& aEntryId) const {
+ const nsLiteralCString descendantUsagesQuery =
+ "WITH RECURSIVE traceChildren(handle, parent) AS ( "
+ "SELECT handle, parent "
+ "FROM Entries "
+ "WHERE handle=:handle "
+ "UNION "
+ "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries "
+ "WHERE traceChildren.handle=Entries.parent ) "
+ "SELECT sum(Usages.usage) "
+ "FROM traceChildren INNER JOIN Usages "
+ "USING(handle) "
+ ";"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(mConnection, descendantUsagesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
+ if (!moreResults) {
+ return 0;
+ }
+
+ QM_TRY_RETURN(stmt.GetUsageByColumn(/* Column */ 0u));
+}
+
+Result<nsTArray<FileId>, QMResult>
+FileSystemDatabaseManagerVersion001::FindFilesUnderEntry(
+ const EntryId& aEntryId) const {
+ nsTArray<FileId> descendants;
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(mConnection, gDescendantsQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
+
+ while (moreResults) {
+ // Works only for version 001
+ QM_TRY_INSPECT(const FileId& fileId,
+ stmt.GetFileIdByColumn(/* Column */ 0u));
+
+ descendants.AppendElement(fileId);
+
+ QM_TRY_UNWRAP(moreResults, stmt.ExecuteStep());
+ }
+ }
+
+ return descendants;
+}
+
+nsresult FileSystemDatabaseManagerVersion001::SetUsageTracking(
+ const FileId& aFileId, bool aTracked) {
+ auto onMissingFile = [this, &aFileId](const auto& aRv) {
+ // Usages constrains entryId to be present in Files
+ MOZ_ASSERT(NS_ERROR_STORAGE_CONSTRAINT == ToNSResult(aRv));
+
+ // The query *should* fail if and only if file does not exist
+ QM_TRY_UNWRAP(DebugOnly<bool> fileExists, DoesFileIdExist(aFileId),
+ QM_VOID);
+ MOZ_ASSERT(!fileExists);
+ };
+
+ return SetUsageTrackingImpl(mConnection, aFileId, aTracked, onMissingFile);
+}
+
+nsresult FileSystemDatabaseManagerVersion001::UpdateUsageInDatabase(
+ const FileId& aFileId, Usage 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.BindFileIdByName("handle"_ns, aFileId)));
+ QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
+
+ return NS_OK;
+}
+
+Result<Ok, QMResult> FileSystemDatabaseManagerVersion001::EnsureUsageIsKnown(
+ const FileId& aFileId) {
+ if (mFilesOfUnknownUsage < 0) { // Lazy initialization
+ QM_TRY_UNWRAP(mFilesOfUnknownUsage, GetTrackedFilesCount(mConnection));
+ }
+
+ if (mFilesOfUnknownUsage == 0) {
+ return Ok{};
+ }
+
+ QM_TRY_UNWRAP(Maybe<Usage> oldUsage,
+ GetMaybeTrackedUsage(mConnection, aFileId));
+ if (oldUsage.isNothing()) {
+ return Ok{}; // Usage is 0 or it was successfully recorded at unlocking.
+ }
+
+ auto quotaCacheUpdate = [this, &aFileId,
+ oldSize = oldUsage.value()](Usage aNewSize) {
+ return UpdateCachedQuotaUsage(aFileId, oldSize, aNewSize);
+ };
+
+ static const nsLiteralCString updateUsagesKeepTrackedQuery =
+ "UPDATE Usages SET usage = :usage WHERE handle = :handle;"_ns;
+
+ // If usage update fails, we log an error and keep things the way they were.
+ QM_TRY(QM_TO_RESULT(UpdateUsageForFileEntry(
+ mConnection, *mFileManager, aFileId, updateUsagesKeepTrackedQuery,
+ std::move(quotaCacheUpdate))),
+ Err(QMResult(NS_ERROR_DOM_FILE_NOT_READABLE_ERR)),
+ ([this, &aFileId](const auto& /*aRv*/) {
+ LogWithFilename(*mFileManager, "Could not read the size of file %s",
+ aFileId);
+ }));
+
+ // We read and updated the quota usage successfully.
+ --mFilesOfUnknownUsage;
+ MOZ_ASSERT(mFilesOfUnknownUsage >= 0);
+
+ return Ok{};
+}
+
+void FileSystemDatabaseManagerVersion001::DecreaseCachedQuotaUsage(
+ int64_t aDelta) {
+ quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ quotaManager->DecreaseUsageForClient(mClientMetadata, aDelta);
+}
+
+nsresult FileSystemDatabaseManagerVersion001::UpdateCachedQuotaUsage(
+ const FileId& aFileId, Usage aOldUsage, Usage aNewUsage) {
+ quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileObj,
+ mFileManager->GetFile(aFileId).mapErr(toNSResult));
+
+ RefPtr<quota::QuotaObject> quotaObject = quotaManager->GetQuotaObject(
+ quota::PERSISTENCE_TYPE_DEFAULT, mClientMetadata,
+ quota::Client::FILESYSTEM, fileObj, aOldUsage);
+ MOZ_ASSERT(quotaObject);
+
+ QM_TRY(OkIf(quotaObject->MaybeUpdateSize(aNewUsage, /* aTruncate */ true)),
+ NS_ERROR_FILE_NO_DEVICE_SPACE);
+
+ return NS_OK;
+}
+
+nsresult FileSystemDatabaseManagerVersion001::ClearDestinationIfNotLocked(
+ const FileSystemConnection& aConnection,
+ const FileSystemDataManager* const aDataManager,
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewDesignation) {
+ // If the destination file exists, fail explicitly. Spec author plans to
+ // revise the spec
+ QM_TRY_UNWRAP(bool exists, DoesFileExist(aConnection, aNewDesignation));
+ if (exists) {
+ QM_TRY_INSPECT(const EntryId& destId,
+ FindEntryId(aConnection, aNewDesignation, true));
+ QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(destId));
+ if (isLocked) {
+ LOG(("Trying to overwrite in-use file"));
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+ }
+
+ QM_TRY_UNWRAP(DebugOnly<bool> isRemoved, RemoveFile(aNewDesignation));
+ MOZ_ASSERT(isRemoved);
+ } else {
+ QM_TRY_UNWRAP(exists, DoesDirectoryExist(aConnection, aNewDesignation));
+ if (exists) {
+ // Fails if directory contains locked files, otherwise total wipeout
+ QM_TRY_UNWRAP(DebugOnly<bool> isRemoved,
+ MOZ_TO_RESULT(RemoveDirectory(aNewDesignation,
+ /* recursive */ true)));
+ MOZ_ASSERT(isRemoved);
+ }
+ }
+
+ return NS_OK;
+}
+
+nsresult FileSystemDatabaseManagerVersion001::PrepareRenameEntry(
+ const FileSystemConnection& aConnection,
+ const FileSystemDataManager* const aDataManager,
+ const FileSystemEntryMetadata& aHandle, const Name& aNewName,
+ bool aIsFile) {
+ const EntryId& entryId = aHandle.entryId();
+
+ // At this point, entry exists
+ if (aIsFile) {
+ QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(entryId));
+ if (isLocked) {
+ LOG(("Trying to move in-use file"));
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+ }
+ }
+
+ // If the destination file exists, fail explicitly.
+ FileSystemChildMetadata destination;
+ QM_TRY_UNWRAP(EntryId parent, FindParent(mConnection, entryId));
+ destination.parentId() = parent;
+ destination.childName() = aNewName;
+
+ QM_TRY(MOZ_TO_RESULT(ClearDestinationIfNotLocked(mConnection, mDataManager,
+ aHandle, destination)));
+
+ return NS_OK;
+}
+
+nsresult FileSystemDatabaseManagerVersion001::PrepareMoveEntry(
+ const FileSystemConnection& aConnection,
+ const FileSystemDataManager* const aDataManager,
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewDesignation, bool aIsFile) {
+ const EntryId& entryId = aHandle.entryId();
+
+ // At this point, entry exists
+ if (aIsFile) {
+ QM_TRY_UNWRAP(const bool isLocked, aDataManager->IsLocked(entryId));
+ if (isLocked) {
+ LOG(("Trying to move in-use file"));
+ return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+ }
+ }
+
+ QM_TRY(QM_TO_RESULT(ClearDestinationIfNotLocked(aConnection, aDataManager,
+ aHandle, aNewDesignation)));
+
+ // XXX: This should be before clearing the target
+
+ // 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(aConnection, {entryId, aNewDesignation.parentId()}));
+ if (isDestinationUnderSelf) {
+ return NS_ERROR_DOM_INVALID_MODIFICATION_ERR;
+ }
+
+ return NS_OK;
+}
+
+/**
+ * Free functions
+ */
+
+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> DoesFileExist(const FileSystemConnection& aConnection,
+ const EntryId& aEntryId) {
+ MOZ_ASSERT(!aEntryId.IsEmpty());
+
+ const nsCString existsQuery =
+ "SELECT EXISTS "
+ "(SELECT 1 FROM Files WHERE handle = :handle ) "
+ ";"_ns;
+
+ QM_TRY_RETURN(ApplyEntryExistsQuery(aConnection, existsQuery, aEntryId));
+}
+
+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));
+}
+
+Result<EntryId, QMResult> FindEntryId(const FileSystemConnection& aConnection,
+ const FileSystemChildMetadata& aHandle,
+ bool aIsFile) {
+ const nsCString aDirectoryQuery =
+ "SELECT Entries.handle FROM Directories "
+ "INNER JOIN Entries USING (handle) "
+ "WHERE Directories.name = :name AND Entries.parent = :parent "
+ ";"_ns;
+
+ const nsCString aFileQuery =
+ "SELECT Entries.handle FROM Files INNER 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;
+}
+
+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;
+}
+
+Result<bool, QMResult> IsSame(const FileSystemConnection& aConnection,
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewHandle,
+ bool aIsFile) {
+ MOZ_ASSERT(!aNewHandle.parentId().IsEmpty());
+
+ // Typically aNewHandle does not exist which is not an error
+ QM_TRY_RETURN(QM_OR_ELSE_LOG_VERBOSE_IF(
+ // Expression.
+ FindEntryId(aConnection, aNewHandle, aIsFile)
+ .map([&aHandle](const EntryId& entryId) {
+ return entryId == aHandle.entryId();
+ }),
+ // Predicate.
+ IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>,
+ // Fallback.
+ ErrToOkFromQMResult<false>));
+}
+
+Result<Path, QMResult> ResolveReversedPath(
+ const FileSystemConnection& aConnection,
+ const FileSystemEntryPair& aEndpoints) {
+ const nsLiteralCString 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;
+}
+
+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;
+}
+
+// TODO: Implement idle maintenance
+void TryRemoveDuringIdleMaintenance(
+ const nsTArray<FileId>& /* aItemToRemove */) {
+ // Not implemented
+}
+
+ContentType DetermineContentType(const Name& aName) {
+ QM_TRY_UNWRAP(
+ auto typeResult,
+ QM_OR_ELSE_LOG_VERBOSE(
+ FileSystemContentTypeGuess::FromPath(aName),
+ ([](const auto& aRv) -> Result<ContentType, QMResult> {
+ const nsresult rv = ToNSResult(aRv);
+ switch (rv) {
+ case NS_ERROR_FAILURE: /* There is an unknown new extension. */
+ return ContentType(""_ns); /* We clear the old extension. */
+
+ case NS_ERROR_INVALID_ARG: /* The name is garbled. */
+ [[fallthrough]];
+ case NS_ERROR_NOT_AVAILABLE: /* There is no extension. */
+ return VoidCString(); /* We keep the old extension. */
+
+ default:
+ MOZ_ASSERT_UNREACHABLE("Should never get here!");
+ return Err(aRv);
+ }
+ })),
+ ContentType(""_ns));
+
+ return typeResult;
+}
+
+} // 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..333c5af6c2
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion001.h
@@ -0,0 +1,208 @@
+/* -*- 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 "mozilla/dom/quota/CommonMetadata.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsString.h"
+
+namespace mozilla::dom::fs {
+
+struct FileId;
+
+namespace data {
+
+class FileSystemDataManager;
+class FileSystemFileManager;
+
+/**
+ * @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);
+
+ /* Static to allow use by quota client without instantiation */
+ static nsresult RescanTrackedUsages(
+ const FileSystemConnection& aConnection,
+ const quota::OriginMetadata& aOriginMetadata);
+
+ /* Static to allow use by quota client without instantiation */
+ static Result<Usage, QMResult> GetFileUsage(
+ const FileSystemConnection& aConnection);
+
+ nsresult UpdateUsage(const FileId& aFileId) override;
+
+ Result<EntryId, QMResult> GetOrCreateDirectory(
+ const FileSystemChildMetadata& aHandle, bool aCreate) override;
+
+ Result<EntryId, QMResult> GetOrCreateFile(
+ const FileSystemChildMetadata& aHandle, bool aCreate) override;
+
+ nsresult GetFile(const EntryId& aEntryId, const FileId& aFileId,
+ const FileMode& aMode, ContentType& aType,
+ TimeStamp& lastModifiedMilliSeconds, Path& aPath,
+ nsCOMPtr<nsIFile>& aFile) const override;
+
+ Result<FileSystemDirectoryListing, QMResult> GetDirectoryEntries(
+ const EntryId& aParent, PageNumber aPage) const override;
+
+ Result<bool, QMResult> RemoveDirectory(const FileSystemChildMetadata& aHandle,
+ bool aRecursive) override;
+
+ Result<bool, QMResult> RemoveFile(
+ const FileSystemChildMetadata& aHandle) override;
+
+ Result<EntryId, QMResult> RenameEntry(const FileSystemEntryMetadata& aHandle,
+ const Name& aNewName) override;
+
+ Result<EntryId, QMResult> MoveEntry(
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewDesignation) override;
+
+ Result<Path, QMResult> Resolve(
+ const FileSystemEntryPair& aEndpoints) const override;
+
+ Result<EntryId, QMResult> GetEntryId(
+ const FileSystemChildMetadata& aHandle) const override;
+
+ Result<EntryId, QMResult> GetEntryId(const FileId& aFileId) const override;
+
+ Result<FileId, QMResult> EnsureFileId(const EntryId& aEntryId) override;
+
+ Result<FileId, QMResult> EnsureTemporaryFileId(
+ const EntryId& aEntryId) override;
+
+ Result<FileId, QMResult> GetFileId(const EntryId& aEntryId) const override;
+
+ nsresult MergeFileId(const EntryId& aEntryId, const FileId& aFileId,
+ bool aAbort) override;
+
+ void Close() override;
+
+ nsresult BeginUsageTracking(const FileId& aFileId) override;
+
+ nsresult EndUsageTracking(const FileId& aFileId) override;
+
+ virtual ~FileSystemDatabaseManagerVersion001() = default;
+
+ protected:
+ virtual Result<bool, QMResult> DoesFileIdExist(const FileId& aFileId) const;
+
+ virtual nsresult RemoveFileId(const FileId& aFileId);
+
+ virtual Result<Usage, QMResult> GetUsagesOfDescendants(
+ const EntryId& aEntryId) const;
+
+ virtual Result<nsTArray<FileId>, QMResult> FindFilesUnderEntry(
+ const EntryId& aEntryId) const;
+
+ nsresult SetUsageTracking(const FileId& aFileId, bool aTracked);
+
+ nsresult UpdateUsageInDatabase(const FileId& aFileId, Usage aNewDiskUsage);
+
+ Result<Ok, QMResult> EnsureUsageIsKnown(const FileId& aFileId);
+
+ void DecreaseCachedQuotaUsage(int64_t aDelta);
+
+ nsresult UpdateCachedQuotaUsage(const FileId& aFileId, Usage aOldUsage,
+ Usage aNewUsage);
+
+ nsresult ClearDestinationIfNotLocked(
+ const FileSystemConnection& aConnection,
+ const FileSystemDataManager* const aDataManager,
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewDesignation);
+
+ nsresult PrepareRenameEntry(const FileSystemConnection& aConnection,
+ const FileSystemDataManager* const aDataManager,
+ const FileSystemEntryMetadata& aHandle,
+ const Name& aNewName, bool aIsFile);
+
+ nsresult PrepareMoveEntry(const FileSystemConnection& aConnection,
+ const FileSystemDataManager* const aDataManager,
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewDesignation,
+ bool aIsFile);
+
+ // 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;
+
+ const quota::ClientMetadata mClientMetadata;
+
+ int32_t mFilesOfUnknownUsage;
+};
+
+inline auto toNSResult = [](const auto& aRv) { return ToNSResult(aRv); };
+
+Result<bool, QMResult> ApplyEntryExistsQuery(
+ const FileSystemConnection& aConnection, const nsACString& aQuery,
+ const FileSystemChildMetadata& aHandle);
+
+Result<bool, QMResult> ApplyEntryExistsQuery(
+ const FileSystemConnection& aConnection, const nsACString& aQuery,
+ const EntryId& aEntry);
+
+Result<bool, QMResult> DoesFileExist(const FileSystemConnection& aConnection,
+ const EntryId& aEntryId);
+
+Result<bool, QMResult> IsFile(const FileSystemConnection& aConnection,
+ const EntryId& aEntryId);
+
+Result<EntryId, QMResult> FindEntryId(const FileSystemConnection& aConnection,
+ const FileSystemChildMetadata& aHandle,
+ bool aIsFile);
+
+Result<EntryId, QMResult> FindParent(const FileSystemConnection& aConnection,
+ const EntryId& aEntryId);
+
+Result<bool, QMResult> IsSame(const FileSystemConnection& aConnection,
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewHandle,
+ bool aIsFile);
+
+Result<Path, QMResult> ResolveReversedPath(
+ const FileSystemConnection& aConnection,
+ const FileSystemEntryPair& aEndpoints);
+
+nsresult GetFileAttributes(const FileSystemConnection& aConnection,
+ const EntryId& aEntryId, ContentType& aType);
+
+void TryRemoveDuringIdleMaintenance(const nsTArray<FileId>& aItemToRemove);
+
+ContentType DetermineContentType(const Name& aName);
+
+} // namespace data
+} // namespace mozilla::dom::fs
+
+#endif // DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION001_H_
diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.cpp b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.cpp
new file mode 100644
index 0000000000..3543346ff0
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.cpp
@@ -0,0 +1,832 @@
+/* -*- 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 "FileSystemDatabaseManagerVersion002.h"
+
+#include "ErrorList.h"
+#include "FileSystemContentTypeGuess.h"
+#include "FileSystemDataManager.h"
+#include "FileSystemFileManager.h"
+#include "FileSystemHashSource.h"
+#include "FileSystemHashStorageFunction.h"
+#include "FileSystemParentTypes.h"
+#include "ResultStatement.h"
+#include "StartedTransaction.h"
+#include "mozStorageHelper.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/dom/FileSystemDataManager.h"
+#include "mozilla/dom/FileSystemHandle.h"
+#include "mozilla/dom/FileSystemLog.h"
+#include "mozilla/dom/FileSystemTypes.h"
+#include "mozilla/dom/PFileSystemManager.h"
+#include "mozilla/dom/QMResult.h"
+#include "mozilla/dom/quota/Client.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/quota/QuotaObject.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+
+namespace mozilla::dom::fs::data {
+
+namespace {
+
+Result<FileId, QMResult> GetFileId002(const FileSystemConnection& aConnection,
+ const EntryId& aEntryId) {
+ const nsLiteralCString fileIdQuery =
+ "SELECT fileId FROM MainFiles WHERE handle = :entryId ;"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, fileIdQuery));
+ 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_INSPECT(const FileId& fileId, stmt.GetFileIdByColumn(/* Column */ 0u));
+
+ return fileId;
+}
+
+Result<bool, QMResult> DoesFileIdExist(const FileSystemConnection& aConnection,
+ const FileId& aFileId) {
+ MOZ_ASSERT(!aFileId.IsEmpty());
+
+ const nsLiteralCString existsQuery =
+ "SELECT EXISTS "
+ "(SELECT 1 FROM FileIds WHERE fileId = :handle ) "
+ ";"_ns;
+
+ QM_TRY_RETURN(
+ ApplyEntryExistsQuery(aConnection, existsQuery, aFileId.Value()));
+}
+
+nsresult RehashFile(const FileSystemConnection& aConnection,
+ const EntryId& aEntryId,
+ const FileSystemChildMetadata& aNewDesignation,
+ const ContentType& aNewType) {
+ QM_TRY_INSPECT(const EntryId& newId,
+ FileSystemHashSource::GenerateHash(
+ aNewDesignation.parentId(), aNewDesignation.childName()));
+
+ // The destination should be empty at this point: either we exited because
+ // overwrite was not desired, or the existing content was removed.
+ const nsLiteralCString insertNewEntryQuery =
+ "INSERT INTO Entries ( handle, parent ) "
+ "VALUES ( :newId, :newParent ) "
+ ";"_ns;
+
+ const nsLiteralCString insertNewFileAndTypeQuery =
+ "INSERT INTO Files ( handle, type, name ) "
+ "VALUES ( :newId, :type, :newName ) "
+ ";"_ns;
+
+ const nsLiteralCString insertNewFileKeepTypeQuery =
+ "INSERT INTO Files ( handle, type, name ) "
+ "SELECT :newId, type, :newName FROM Files "
+ "WHERE handle = :oldId ;"_ns;
+
+ const auto& insertNewFileQuery = aNewType.IsVoid()
+ ? insertNewFileKeepTypeQuery
+ : insertNewFileAndTypeQuery;
+
+ const nsLiteralCString updateFileMappingsQuery =
+ "UPDATE FileIds SET handle = :newId WHERE handle = :handle ;"_ns;
+
+ const nsLiteralCString updateMainFilesQuery =
+ "UPDATE MainFiles SET handle = :newId WHERE handle = :handle ;"_ns;
+
+ const nsLiteralCString cleanupOldEntryQuery =
+ "DELETE FROM Entries WHERE handle = :handle ;"_ns;
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection));
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertNewEntryQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId)));
+ QM_TRY(QM_TO_RESULT(
+ stmt.BindEntryIdByName("newParent"_ns, aNewDesignation.parentId())));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertNewFileQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId)));
+ if (aNewType.IsVoid()) {
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("oldId"_ns, aEntryId)));
+ } else {
+ QM_TRY(QM_TO_RESULT(stmt.BindContentTypeByName("type"_ns, aNewType)));
+ }
+ QM_TRY(QM_TO_RESULT(
+ stmt.BindNameByName("newName"_ns, aNewDesignation.childName())));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(
+ ResultStatement stmt,
+ ResultStatement::Create(aConnection, updateFileMappingsQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId)));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, updateMainFilesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("newId"_ns, newId)));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, cleanupOldEntryQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+
+ return NS_OK;
+}
+
+nsresult RehashDirectory(const FileSystemConnection& aConnection,
+ const EntryId& aEntryId,
+ const FileSystemChildMetadata& aNewDesignation) {
+ // This name won't match up with the entryId for the old path but
+ // it will be removed at the end
+ const nsLiteralCString updateNameQuery =
+ "UPDATE Directories SET name = :newName WHERE handle = :handle "
+ "; "_ns;
+
+ const nsLiteralCString calculateHashesQuery =
+ "CREATE TEMPORARY TABLE ParentChildHash AS "
+ "WITH RECURSIVE "
+ "rehashMap( depth, isFile, handle, parent, name, hash ) AS ( "
+ "SELECT 0, isFile, handle, parent, name, hashEntry( :newParent, name ) "
+ "FROM EntryNames WHERE handle = :handle UNION SELECT "
+ "1 + depth, EntryNames.isFile, EntryNames.handle, EntryNames.parent, "
+ "EntryNames.name, hashEntry( rehashMap.hash, EntryNames.name ) "
+ "FROM rehashMap, EntryNames WHERE rehashMap.handle = EntryNames.parent ) "
+ "SELECT depth, isFile, handle, parent, name, hash FROM rehashMap "
+ ";"_ns;
+
+ const nsLiteralCString createIndexByDepthQuery =
+ "CREATE INDEX indexOnDepth ON ParentChildHash ( depth ); "_ns;
+
+ // To avoid constraint violation, we insert new entries under the old parent.
+ // The destination should be empty at this point: either we exited because
+ // overwrite was not desired, or the existing content was removed.
+ const nsLiteralCString insertNewEntriesQuery =
+ "INSERT INTO Entries ( handle, parent ) "
+ "SELECT hash, :parent FROM ParentChildHash "
+ ";"_ns;
+
+ const nsLiteralCString insertNewDirectoriesQuery =
+ "INSERT INTO Directories ( handle, name ) "
+ "SELECT hash, name FROM ParentChildHash WHERE isFile = 0 "
+ "ORDER BY depth "
+ ";"_ns;
+
+ const nsLiteralCString insertNewFilesQuery =
+ "INSERT INTO Files ( handle, type, name ) "
+ "SELECT ParentChildHash.hash, Files.type, ParentChildHash.name "
+ "FROM ParentChildHash INNER JOIN Files USING (handle) "
+ "WHERE ParentChildHash.isFile = 1 "
+ ";"_ns;
+
+ const nsLiteralCString updateFileMappingsQuery =
+ "UPDATE FileIds SET handle = hash "
+ "FROM ( SELECT handle, hash FROM ParentChildHash ) AS replacement "
+ "WHERE FileIds.handle = replacement.handle "
+ ";"_ns;
+
+ const nsLiteralCString updateMainFilesQuery =
+ "UPDATE MainFiles SET handle = hash "
+ "FROM ( SELECT handle, hash FROM ParentChildHash ) AS replacement "
+ "WHERE MainFiles.handle = replacement.handle "
+ ";"_ns;
+
+ // Now fix the parents
+ const nsLiteralCString updateEntryMappingsQuery =
+ "UPDATE Entries SET parent = hash "
+ "FROM ( SELECT Lhs.hash AS handle, Rhs.hash AS hash, Lhs.depth AS depth "
+ "FROM ParentChildHash AS Lhs "
+ "INNER JOIN ParentChildHash AS Rhs "
+ "ON Rhs.handle = Lhs.parent ORDER BY depth ) AS replacement "
+ "WHERE Entries.handle = replacement.handle "
+ ";"_ns;
+
+ const nsLiteralCString cleanupOldEntriesQuery =
+ "DELETE FROM Entries WHERE handle = :handle "
+ ";"_ns;
+
+ // Index is automatically deleted
+ const nsLiteralCString cleanupTemporaries =
+ "DROP TABLE ParentChildHash "
+ ";"_ns;
+
+ nsCOMPtr<mozIStorageFunction> rehashFunction =
+ new data::FileSystemHashStorageFunction();
+ QM_TRY(MOZ_TO_RESULT(aConnection->CreateFunction("hashEntry"_ns,
+ /* number of arguments */ 2,
+ rehashFunction)));
+ auto finallyRemoveFunction = MakeScopeExit([&aConnection]() {
+ QM_WARNONLY_TRY(MOZ_TO_RESULT(aConnection->RemoveFunction("hashEntry"_ns)));
+ });
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection));
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, updateNameQuery));
+ QM_TRY(QM_TO_RESULT(
+ stmt.BindNameByName("newName"_ns, aNewDesignation.childName())));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, calculateHashesQuery));
+ QM_TRY(QM_TO_RESULT(
+ stmt.BindEntryIdByName("newParent"_ns, aNewDesignation.parentId())));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(createIndexByDepthQuery)));
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertNewEntriesQuery));
+ QM_TRY(QM_TO_RESULT(
+ stmt.BindEntryIdByName("parent"_ns, aNewDesignation.parentId())));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(
+ ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertNewDirectoriesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertNewFilesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(
+ ResultStatement stmt,
+ ResultStatement::Create(aConnection, updateFileMappingsQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, updateMainFilesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(
+ ResultStatement stmt,
+ ResultStatement::Create(aConnection, updateEntryMappingsQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, cleanupOldEntriesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, cleanupTemporaries));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+
+ return NS_OK;
+}
+
+/**
+ * @brief Each entryId is interpreted as a large integer, which is increased
+ * until an unused value is found. This process is in principle infallible.
+ * The files associated with a given path will form a cluster next to the
+ * entryId which could be used for recovery because our hash function is
+ * expected to distribute all clusters far from each other.
+ */
+Result<FileId, QMResult> GetNextFreeFileId(
+ const FileSystemConnection& aConnection,
+ const FileSystemFileManager& aFileManager, const EntryId& aEntryId) {
+ MOZ_ASSERT(32u == aEntryId.Length());
+
+ auto DoesExist = [&aConnection, &aFileManager](
+ const FileId& aId) -> Result<bool, QMResult> {
+ QM_TRY_INSPECT(const nsCOMPtr<nsIFile>& diskFile,
+ aFileManager.GetFile(aId));
+
+ bool result = true;
+ QM_TRY(QM_TO_RESULT(diskFile->Exists(&result)));
+ if (result) {
+ return true;
+ }
+
+ QM_TRY_RETURN(DoesFileIdExist(aConnection, aId));
+ };
+
+ auto Next = [](FileId& aId) {
+ // Using a larger integer would make fileIds depend on platform endianness.
+ using IntegerType = uint8_t;
+ constexpr int32_t bufferSize = 32 / sizeof(IntegerType);
+ using IdBuffer = std::array<IntegerType, bufferSize>;
+
+ auto Increase = [](IdBuffer& aIn) {
+ for (int i = 0; i < bufferSize; ++i) {
+ if (1u + aIn[i] != 0u) {
+ ++aIn[i];
+ return;
+ }
+ aIn[i] = 0u;
+ }
+ };
+
+ DebugOnly<nsCString> original = aId.Value();
+ Increase(*reinterpret_cast<IdBuffer*>(aId.mValue.BeginWriting()));
+ MOZ_ASSERT(!aId.Value().Equals(original));
+ };
+
+ FileId id = FileId(aEntryId);
+
+ while (true) {
+ QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeExists, DoesExist(id));
+ if (maybeExists.isSome() && !maybeExists.value()) {
+ return id;
+ }
+
+ Next(id);
+ }
+}
+
+Result<FileId, QMResult> AddNewFileId(const FileSystemConnection& aConnection,
+ const FileSystemFileManager& aFileManager,
+ const EntryId& aEntryId) {
+ QM_TRY_INSPECT(const FileId& nextFreeId,
+ GetNextFreeFileId(aConnection, aFileManager, aEntryId));
+
+ const nsLiteralCString insertNewFileIdQuery =
+ "INSERT INTO FileIds ( fileId, handle ) "
+ "VALUES ( :fileId, :entryId ) "
+ "; "_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertNewFileIdQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, nextFreeId)));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId)));
+
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+
+ return nextFreeId;
+}
+
+/**
+ * @brief Get recorded usage or zero if nothing was ever written to the file.
+ * Removing files is only allowed when there is no lock on the file, and their
+ * usage is either correctly recorded in the database during unlock, or nothing,
+ * or they remain in tracked state and the quota manager assumes their usage to
+ * be equal to the latest recorded value. In all cases, the latest recorded
+ * value (or nothing) is the correct amount of quota to be released.
+ */
+Result<Usage, QMResult> GetKnownUsage(const FileSystemConnection& aConnection,
+ const FileId& aFileId) {
+ const nsLiteralCString trackedUsageQuery =
+ "SELECT usage FROM Usages WHERE handle = :handle ;"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, trackedUsageQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("handle"_ns, aFileId)));
+
+ QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
+ if (!moreResults) {
+ return 0;
+ }
+
+ QM_TRY_RETURN(stmt.GetUsageByColumn(/* Column */ 0u));
+}
+
+} // namespace
+
+/* static */
+nsresult FileSystemDatabaseManagerVersion002::RescanTrackedUsages(
+ const FileSystemConnection& aConnection,
+ const quota::OriginMetadata& aOriginMetadata) {
+ return FileSystemDatabaseManagerVersion001::RescanTrackedUsages(
+ aConnection, aOriginMetadata);
+}
+
+/* static */
+Result<Usage, QMResult> FileSystemDatabaseManagerVersion002::GetFileUsage(
+ const FileSystemConnection& aConnection) {
+ return FileSystemDatabaseManagerVersion001::GetFileUsage(aConnection);
+}
+
+nsresult FileSystemDatabaseManagerVersion002::GetFile(
+ const EntryId& aEntryId, const FileId& aFileId, const FileMode& aMode,
+ ContentType& aType, TimeStamp& lastModifiedMilliSeconds,
+ nsTArray<Name>& aPath, nsCOMPtr<nsIFile>& aFile) const {
+ MOZ_ASSERT(!aFileId.IsEmpty());
+
+ const FileSystemEntryPair endPoints(mRootEntry, aEntryId);
+ QM_TRY_UNWRAP(aPath, ResolveReversedPath(mConnection, endPoints));
+ if (aPath.IsEmpty()) {
+ return NS_ERROR_DOM_NOT_FOUND_ERR;
+ }
+
+ QM_TRY(MOZ_TO_RESULT(GetFileAttributes(mConnection, aEntryId, aType)));
+
+ if (aMode == FileMode::SHARED_FROM_COPY) {
+ QM_WARNONLY_TRY_UNWRAP(Maybe<FileId> mainFileId, GetFileId(aEntryId));
+ if (mainFileId) {
+ QM_TRY_UNWRAP(aFile,
+ mFileManager->CreateFileFrom(aFileId, mainFileId.value()));
+ } else {
+ // LockShared/EnsureTemporaryFileId has provided a brand new fileId.
+ QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId));
+ }
+ } else {
+ MOZ_ASSERT(aMode == FileMode::EXCLUSIVE ||
+ aMode == FileMode::SHARED_FROM_EMPTY);
+
+ QM_TRY_UNWRAP(aFile, mFileManager->GetOrCreateFile(aFileId));
+ }
+
+ PRTime lastModTime = 0;
+ QM_TRY(MOZ_TO_RESULT(aFile->GetLastModifiedTime(&lastModTime)));
+ lastModifiedMilliSeconds = static_cast<TimeStamp>(lastModTime);
+
+ aPath.Reverse();
+
+ return NS_OK;
+}
+
+Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::RenameEntry(
+ const FileSystemEntryMetadata& aHandle, const Name& aNewName) {
+ MOZ_ASSERT(!aNewName.IsEmpty());
+
+ const auto& entryId = aHandle.entryId();
+ MOZ_ASSERT(!entryId.IsEmpty());
+
+ // Can't rename root
+ if (mRootEntry == entryId) {
+ return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
+ }
+
+ // Verify the source exists
+ QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId),
+ Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)));
+
+ // Are we actually renaming?
+ if (aHandle.entryName() == aNewName) {
+ return entryId;
+ }
+
+ QM_TRY(QM_TO_RESULT(PrepareRenameEntry(mConnection, mDataManager, aHandle,
+ aNewName, isFile)));
+
+ QM_TRY_UNWRAP(EntryId parentId, FindParent(mConnection, entryId));
+ FileSystemChildMetadata newDesignation(parentId, aNewName);
+
+ if (isFile) {
+ const ContentType type = DetermineContentType(aNewName);
+ QM_TRY(
+ QM_TO_RESULT(RehashFile(mConnection, entryId, newDesignation, type)));
+ } else {
+ QM_TRY(QM_TO_RESULT(RehashDirectory(mConnection, entryId, newDesignation)));
+ }
+
+ QM_TRY_UNWRAP(DebugOnly<EntryId> dbId,
+ FindEntryId(mConnection, newDesignation, isFile));
+ QM_TRY_UNWRAP(EntryId generated,
+ FileSystemHashSource::GenerateHash(parentId, aNewName));
+ MOZ_ASSERT(static_cast<EntryId&>(dbId).Equals(generated));
+
+ return generated;
+}
+
+Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::MoveEntry(
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewDesignation) {
+ MOZ_ASSERT(!aHandle.entryId().IsEmpty());
+
+ const auto& entryId = aHandle.entryId();
+
+ if (mRootEntry == entryId) {
+ return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
+ }
+
+ // Verify the source exists
+ QM_TRY_UNWRAP(bool isFile, IsFile(mConnection, entryId),
+ Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR)));
+
+ // If the rename doesn't change the name or directory, just return success.
+ // XXX Needs to be added to the spec
+ QM_WARNONLY_TRY_UNWRAP(Maybe<bool> maybeSame,
+ IsSame(mConnection, aHandle, aNewDesignation, isFile));
+ if (maybeSame && maybeSame.value()) {
+ return entryId;
+ }
+
+ QM_TRY(QM_TO_RESULT(PrepareMoveEntry(mConnection, mDataManager, aHandle,
+ aNewDesignation, isFile)));
+
+ if (isFile) {
+ const ContentType type = DetermineContentType(aNewDesignation.childName());
+ QM_TRY(
+ QM_TO_RESULT(RehashFile(mConnection, entryId, aNewDesignation, type)));
+ } else {
+ QM_TRY(
+ QM_TO_RESULT(RehashDirectory(mConnection, entryId, aNewDesignation)));
+ }
+
+ QM_TRY_UNWRAP(DebugOnly<EntryId> dbId,
+ FindEntryId(mConnection, aNewDesignation, isFile));
+ QM_TRY_UNWRAP(EntryId generated,
+ FileSystemHashSource::GenerateHash(
+ aNewDesignation.parentId(), aNewDesignation.childName()));
+ MOZ_ASSERT(static_cast<EntryId&>(dbId).Equals(generated));
+
+ return generated;
+}
+
+Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::GetEntryId(
+ const FileSystemChildMetadata& aHandle) const {
+ return fs::data::GetEntryHandle(aHandle);
+}
+
+Result<EntryId, QMResult> FileSystemDatabaseManagerVersion002::GetEntryId(
+ const FileId& aFileId) const {
+ const nsLiteralCString getEntryIdQuery =
+ "SELECT handle FROM FileIds WHERE fileId = :fileId ;"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(mConnection, getEntryIdQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aFileId)));
+ QM_TRY_UNWRAP(bool hasEntries, stmt.ExecuteStep());
+
+ if (!hasEntries || stmt.IsNullByColumn(/* Column */ 0u)) {
+ return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
+ }
+
+ QM_TRY_RETURN(stmt.GetEntryIdByColumn(/* Column */ 0u));
+}
+
+Result<FileId, QMResult> FileSystemDatabaseManagerVersion002::EnsureFileId(
+ const EntryId& aEntryId) {
+ QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, aEntryId));
+ if (!exists) {
+ return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
+ }
+
+ QM_TRY_UNWRAP(Maybe<FileId> maybeMainFileId,
+ QM_OR_ELSE_LOG_VERBOSE_IF(
+ // Expression.
+ GetFileId(aEntryId).map([](auto mainFileId) {
+ return Some(std::move(mainFileId));
+ }),
+ // Predicate.
+ IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>,
+ // Fallback.
+ ([](const auto&) -> Result<Maybe<FileId>, QMResult> {
+ return Maybe<FileId>{};
+ })));
+
+ if (maybeMainFileId) {
+ return *maybeMainFileId;
+ }
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
+
+ QM_TRY_INSPECT(const FileId& fileId,
+ AddNewFileId(mConnection, *mFileManager, aEntryId));
+
+ QM_TRY(QM_TO_RESULT(MergeFileId(aEntryId, fileId, /* aAbort */ false)));
+
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+
+ return fileId;
+}
+
+Result<FileId, QMResult>
+FileSystemDatabaseManagerVersion002::EnsureTemporaryFileId(
+ const EntryId& aEntryId) {
+ QM_TRY_UNWRAP(bool exists, DoesFileExist(mConnection, aEntryId));
+ if (!exists) {
+ return Err(QMResult(NS_ERROR_DOM_NOT_FOUND_ERR));
+ }
+
+ QM_TRY_RETURN(AddNewFileId(mConnection, *mFileManager, aEntryId));
+}
+
+Result<FileId, QMResult> FileSystemDatabaseManagerVersion002::GetFileId(
+ const EntryId& aEntryId) const {
+ MOZ_ASSERT(mConnection);
+ return data::GetFileId002(mConnection, aEntryId);
+}
+
+nsresult FileSystemDatabaseManagerVersion002::MergeFileId(
+ const EntryId& aEntryId, const FileId& aFileId, bool aAbort) {
+ MOZ_ASSERT(mConnection);
+
+ auto doCleanUp = [this](const FileId& aCleanable) -> nsresult {
+ // We need to clean up the old main file.
+ QM_TRY_UNWRAP(Usage usage,
+ GetKnownUsage(mConnection, aCleanable).mapErr(toNSResult));
+
+ QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> removedUsage,
+ mFileManager->RemoveFile(aCleanable));
+
+ if (removedUsage) {
+ // Removal of file data was ok, update the related fileId and usage
+ QM_WARNONLY_TRY(QM_TO_RESULT(RemoveFileId(aCleanable)));
+
+ if (usage > 0) { // Performance!
+ DecreaseCachedQuotaUsage(usage);
+ }
+
+ // We only check the most common case. This can fail spuriously if an
+ // external application writes to the file, or OS reports zero size due to
+ // corruption.
+ MOZ_ASSERT_IF(0 == mFilesOfUnknownUsage, usage == removedUsage.value());
+
+ return NS_OK;
+ }
+
+ // Removal failed
+ const nsLiteralCString forgetCleanable =
+ "UPDATE FileIds SET handle = NULL WHERE fileId = :fileId ; "_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(mConnection, forgetCleanable)
+ .mapErr(toNSResult));
+ QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aCleanable)));
+ QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
+
+ TryRemoveDuringIdleMaintenance({aCleanable});
+
+ return NS_OK;
+ };
+
+ if (aAbort) {
+ QM_TRY(MOZ_TO_RESULT(doCleanUp(aFileId)));
+
+ return NS_OK;
+ }
+
+ QM_TRY_UNWRAP(
+ Maybe<FileId> maybeOldFileId,
+ QM_OR_ELSE_LOG_VERBOSE_IF(
+ // Expression.
+ GetFileId(aEntryId)
+ .map([](auto oldFileId) { return Some(std::move(oldFileId)); })
+ .mapErr(toNSResult),
+ // Predicate.
+ IsSpecificError<NS_ERROR_DOM_NOT_FOUND_ERR>,
+ // Fallback.
+ ErrToDefaultOk<Maybe<FileId>>));
+
+ if (maybeOldFileId && *maybeOldFileId == aFileId) {
+ return NS_OK; // Nothing to do
+ }
+
+ // Main file changed
+ const nsLiteralCString flagAsMainFileQuery =
+ "INSERT INTO MainFiles ( handle, fileId ) "
+ "VALUES ( :entryId, :fileId ) "
+ "ON CONFLICT (handle) "
+ "DO UPDATE SET fileId = excluded.fileId "
+ "; "_ns;
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(mConnection));
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(mConnection, flagAsMainFileQuery)
+ .mapErr(toNSResult));
+ QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, aEntryId)));
+ QM_TRY(MOZ_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, aFileId)));
+
+ QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
+
+ if (!maybeOldFileId) {
+ // We successfully added a new main file and there is nothing to clean up.
+ QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
+
+ return NS_OK;
+ }
+
+ MOZ_ASSERT(maybeOldFileId);
+ MOZ_ASSERT(*maybeOldFileId != aFileId);
+
+ QM_TRY(MOZ_TO_RESULT(doCleanUp(*maybeOldFileId)));
+
+ // If the old fileId and usage were not deleted, main file update fails.
+ QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
+
+ return NS_OK;
+}
+
+Result<bool, QMResult> FileSystemDatabaseManagerVersion002::DoesFileIdExist(
+ const FileId& aFileId) const {
+ QM_TRY_RETURN(data::DoesFileIdExist(mConnection, aFileId));
+}
+
+nsresult FileSystemDatabaseManagerVersion002::RemoveFileId(
+ const FileId& aFileId) {
+ const nsLiteralCString removeFileIdQuery =
+ "DELETE FROM FileIds "
+ "WHERE fileId = :fileId "
+ ";"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(mConnection, removeFileIdQuery)
+ .mapErr(toNSResult));
+
+ QM_TRY(MOZ_TO_RESULT(stmt.BindEntryIdByName("fileId"_ns, aFileId.Value())));
+
+ return stmt.Execute();
+}
+
+Result<Usage, QMResult>
+FileSystemDatabaseManagerVersion002::GetUsagesOfDescendants(
+ const EntryId& aEntryId) const {
+ const nsLiteralCString descendantUsagesQuery =
+ "WITH RECURSIVE traceChildren(handle, parent) AS ( "
+ "SELECT handle, parent FROM Entries WHERE handle = :handle "
+ "UNION "
+ "SELECT Entries.handle, Entries.parent FROM traceChildren, Entries "
+ "WHERE traceChildren.handle=Entries.parent ) "
+ "SELECT sum(Usages.usage) "
+ "FROM traceChildren "
+ "INNER JOIN FileIds ON traceChildren.handle = FileIds.handle "
+ "INNER JOIN Usages ON Usages.handle = FileIds.fileId "
+ ";"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(mConnection, descendantUsagesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+ QM_TRY_UNWRAP(const bool moreResults, stmt.ExecuteStep());
+ if (!moreResults) {
+ return 0;
+ }
+
+ QM_TRY_RETURN(stmt.GetUsageByColumn(/* Column */ 0u));
+}
+
+Result<nsTArray<FileId>, QMResult>
+FileSystemDatabaseManagerVersion002::FindFilesUnderEntry(
+ const EntryId& aEntryId) const {
+ 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 FileIds.fileId "
+ "FROM traceChildren INNER JOIN FileIds USING (handle) "
+ ";"_ns;
+
+ nsTArray<FileId> descendants;
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(mConnection, descendantsQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("handle"_ns, aEntryId)));
+
+ while (true) {
+ QM_TRY_INSPECT(const bool& moreResults, stmt.ExecuteStep());
+ if (!moreResults) {
+ break;
+ }
+
+ QM_TRY_INSPECT(const FileId& fileId,
+ stmt.GetFileIdByColumn(/* Column */ 0u));
+ descendants.AppendElement(fileId);
+ }
+ }
+
+ return descendants;
+}
+
+} // namespace mozilla::dom::fs::data
diff --git a/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.h b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.h
new file mode 100644
index 0000000000..6dec629632
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemDatabaseManagerVersion002.h
@@ -0,0 +1,75 @@
+/* -*- 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_FILESYSTEMDATABASEMANAGERVERSION002_H_
+#define DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION002_H_
+
+#include "FileSystemDatabaseManagerVersion001.h"
+
+namespace mozilla::dom::fs::data {
+
+class FileSystemDatabaseManagerVersion002
+ : public FileSystemDatabaseManagerVersion001 {
+ public:
+ FileSystemDatabaseManagerVersion002(
+ FileSystemDataManager* aDataManager, FileSystemConnection&& aConnection,
+ UniquePtr<FileSystemFileManager>&& aFileManager,
+ const EntryId& aRootEntry)
+ : FileSystemDatabaseManagerVersion001(
+ aDataManager, std::move(aConnection), std::move(aFileManager),
+ aRootEntry) {}
+
+ /* Static to allow use by quota client without instantiation */
+ static nsresult RescanTrackedUsages(
+ const FileSystemConnection& aConnection,
+ const quota::OriginMetadata& aOriginMetadata);
+
+ /* Static to allow use by quota client without instantiation */
+ static Result<Usage, QMResult> GetFileUsage(
+ const FileSystemConnection& aConnection);
+
+ nsresult GetFile(const EntryId& aEntryId, const FileId& aFileId,
+ const FileMode& aMode, ContentType& aType,
+ TimeStamp& lastModifiedMilliSeconds, Path& aPath,
+ nsCOMPtr<nsIFile>& aFile) const override;
+
+ Result<EntryId, QMResult> RenameEntry(const FileSystemEntryMetadata& aHandle,
+ const Name& aNewName) override;
+
+ Result<EntryId, QMResult> MoveEntry(
+ const FileSystemEntryMetadata& aHandle,
+ const FileSystemChildMetadata& aNewDesignation) override;
+
+ Result<EntryId, QMResult> GetEntryId(
+ const FileSystemChildMetadata& aHandle) const override;
+
+ Result<EntryId, QMResult> GetEntryId(const FileId& aFileId) const override;
+
+ Result<FileId, QMResult> EnsureFileId(const EntryId& aEntryId) override;
+
+ Result<FileId, QMResult> EnsureTemporaryFileId(
+ const EntryId& aEntryId) override;
+
+ Result<FileId, QMResult> GetFileId(const EntryId& aEntryId) const override;
+
+ nsresult MergeFileId(const EntryId& aEntryId, const FileId& aFileId,
+ bool aAbort) override;
+
+ protected:
+ Result<bool, QMResult> DoesFileIdExist(const FileId& aFileId) const override;
+
+ nsresult RemoveFileId(const FileId& aFileId) override;
+
+ Result<Usage, QMResult> GetUsagesOfDescendants(
+ const EntryId& aEntryId) const override;
+
+ Result<nsTArray<FileId>, QMResult> FindFilesUnderEntry(
+ const EntryId& aEntryId) const override;
+};
+
+} // namespace mozilla::dom::fs::data
+
+#endif // DOM_FS_PARENT_DATAMODEL_FILESYSTEMDATABASEMANAGERVERSION002_H_
diff --git a/dom/fs/parent/datamodel/FileSystemFileManager.cpp b/dom/fs/parent/datamodel/FileSystemFileManager.cpp
new file mode 100644
index 0000000000..02a69467dc
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemFileManager.cpp
@@ -0,0 +1,393 @@
+/* -*- 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 "FileSystemParentTypes.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/NotNull.h"
+#include "mozilla/Result.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/Unused.h"
+#include "mozilla/dom/quota/QuotaManager.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIFile.h"
+#include "nsIFileProtocolHandler.h"
+#include "nsIFileURL.h"
+#include "nsIURIMutator.h"
+#include "nsTHashMap.h"
+#include "nsXPCOM.h"
+
+namespace mozilla::dom::fs::data {
+
+namespace {
+
+constexpr nsLiteralString kDatabaseFileName = u"metadata.sqlite"_ns;
+
+Result<nsCOMPtr<nsIFile>, QMResult> GetFileDestination(
+ const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) {
+ MOZ_ASSERT(32u == aFileId.Value().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(aFileId));
+
+ 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());
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result,
+ QM_TO_RESULT_TRANSFORM(quota::QM_NewLocalFile(aFilePath)));
+
+ 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 FileId& aFileId) {
+ MOZ_ASSERT(!aFileId.IsEmpty());
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
+ GetFileDestination(aTopDirectory, aFileId));
+
+ nsString desiredPath;
+ QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath)));
+
+ QM_TRY_RETURN(QM_TO_RESULT_TRANSFORM(quota::QM_NewLocalFile(desiredPath)));
+}
+
+Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFile(
+ const nsCOMPtr<nsIFile>& aTopDirectory, const FileId& aFileId) {
+ MOZ_ASSERT(!aFileId.IsEmpty());
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
+ GetFileDestination(aTopDirectory, aFileId));
+
+ nsString desiredPath;
+ QM_TRY(QM_TO_RESULT(pathObject->GetPath(desiredPath)));
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> result, GetOrCreateFileImpl(desiredPath));
+
+ return result;
+}
+
+nsresult RemoveFileObject(const nsCOMPtr<nsIFile>& aFilePtr) {
+ // If we cannot tell whether the object is file or directory, or it is a
+ // directory, it is abandoned as an unknown object. If an attempt is made to
+ // create a new object with the same path on disk, we regenerate the FileId
+ // until the collision is resolved.
+
+ bool isFile = false;
+ QM_TRY(MOZ_TO_RESULT(aFilePtr->IsFile(&isFile)));
+
+ QM_TRY(OkIf(isFile), NS_ERROR_FILE_IS_DIRECTORY);
+
+ QM_TRY(QM_TO_RESULT(aFilePtr->Remove(/* recursive */ false)));
+
+ return NS_OK;
+}
+
+#ifdef DEBUG
+// Unused in release builds
+Result<Usage, QMResult> GetFileSize(const nsCOMPtr<nsIFile>& aFileObject) {
+ bool exists = false;
+ QM_TRY(QM_TO_RESULT(aFileObject->Exists(&exists)));
+
+ if (!exists) {
+ return 0;
+ }
+
+ bool isFile = false;
+ QM_TRY(QM_TO_RESULT(aFileObject->IsFile(&isFile)));
+
+ // We never create directories with this path: this is an unknown object
+ // and the file does not exist
+ QM_TRY(OkIf(isFile), 0);
+
+ QM_TRY_UNWRAP(Usage fileSize,
+ QM_TO_RESULT_INVOKE_MEMBER(aFileObject, GetFileSize));
+
+ return fileSize;
+}
+#endif
+
+} // namespace
+
+Result<nsCOMPtr<nsIFile>, QMResult> GetFileSystemDirectory(
+ const quota::OriginMetadata& aOriginMetadata) {
+ MOZ_ASSERT(aOriginMetadata.mPersistenceType ==
+ quota::PERSISTENCE_TYPE_DEFAULT);
+
+ quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> fileSystemDirectory,
+ QM_TO_RESULT_TRANSFORM(
+ quotaManager->GetOriginDirectory(aOriginMetadata)));
+
+ QM_TRY(QM_TO_RESULT(fileSystemDirectory->AppendRelativePath(
+ NS_LITERAL_STRING_FROM_CSTRING(FILESYSTEM_DIRECTORY_NAME))));
+
+ return fileSystemDirectory;
+}
+
+nsresult EnsureFileSystemDirectory(
+ const quota::OriginMetadata& aOriginMetadata) {
+ quota::QuotaManager* quotaManager = quota::QuotaManager::Get();
+ MOZ_ASSERT(quotaManager);
+
+ QM_TRY(MOZ_TO_RESULT(
+ quotaManager->EnsureTemporaryStorageIsInitializedInternal()));
+
+ 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 quota::OriginMetadata& aOriginMetadata) {
+ MOZ_ASSERT(!aOriginMetadata.mOrigin.IsEmpty());
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile,
+ GetFileSystemDirectory(aOriginMetadata));
+
+ QM_TRY(QM_TO_RESULT(databaseFile->AppendRelativePath(kDatabaseFileName)));
+
+ return databaseFile;
+}
+
+/**
+ * TODO: This is almost identical to the corresponding function of IndexedDB
+ */
+Result<nsCOMPtr<nsIFileURL>, QMResult> GetDatabaseFileURL(
+ const quota::OriginMetadata& aOriginMetadata,
+ const int64_t aDirectoryLockId) {
+ MOZ_ASSERT(aDirectoryLockId >= -1);
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> databaseFile,
+ GetDatabaseFile(aOriginMetadata));
+
+ 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)));
+
+ // aDirectoryLockId should only be -1 when we are called from
+ // FileSystemQuotaClient::InitOrigin when the temporary storage hasn't been
+ // initialized yet. At that time, the in-memory objects (e.g. OriginInfo) are
+ // only being created so it doesn't make sense to tunnel quota information to
+ // QuotaVFS to get corresponding QuotaObject instances for SQLite files.
+ const nsCString directoryLockIdClause =
+ "&directoryLockId="_ns + IntToCString(aDirectoryLockId);
+
+ nsCOMPtr<nsIFileURL> result;
+ QM_TRY(QM_TO_RESULT(NS_MutateURI(mutator)
+ .SetQuery("cache=private"_ns + directoryLockIdClause)
+ .Finalize(result)));
+
+ return result;
+}
+
+/* static */
+Result<FileSystemFileManager, QMResult>
+FileSystemFileManager::CreateFileSystemFileManager(
+ nsCOMPtr<nsIFile>&& topDirectory) {
+ return FileSystemFileManager(std::move(topDirectory));
+}
+
+/* static */
+Result<UniquePtr<FileSystemFileManager>, QMResult>
+FileSystemFileManager::CreateFileSystemFileManager(
+ const quota::OriginMetadata& aOriginMetadata) {
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> topDirectory,
+ GetFileSystemDirectory(aOriginMetadata));
+
+ return MakeUnique<FileSystemFileManager>(
+ FileSystemFileManager(std::move(topDirectory)));
+}
+
+FileSystemFileManager::FileSystemFileManager(nsCOMPtr<nsIFile>&& aTopDirectory)
+ : mTopDirectory(std::move(aTopDirectory)) {}
+
+Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetFile(
+ const FileId& aFileId) const {
+ return data::GetFile(mTopDirectory, aFileId);
+}
+
+Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::GetOrCreateFile(
+ const FileId& aFileId) {
+ return data::GetOrCreateFile(mTopDirectory, aFileId);
+}
+
+Result<nsCOMPtr<nsIFile>, QMResult> FileSystemFileManager::CreateFileFrom(
+ const FileId& aDestinationFileId, const FileId& aSourceFileId) {
+ MOZ_ASSERT(!aDestinationFileId.IsEmpty());
+ MOZ_ASSERT(!aSourceFileId.IsEmpty());
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> original, GetFile(aSourceFileId));
+
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> destination,
+ GetFileDestination(mTopDirectory, aDestinationFileId));
+
+ nsAutoString leafName;
+ QM_TRY(QM_TO_RESULT(destination->GetLeafName(leafName)));
+
+ nsCOMPtr<nsIFile> destParent;
+ QM_TRY(QM_TO_RESULT(destination->GetParent(getter_AddRefs(destParent))));
+
+ QM_TRY(QM_TO_RESULT(original->CopyTo(destParent, leafName)));
+
+#ifdef DEBUG
+ bool exists = false;
+ QM_TRY(QM_TO_RESULT(destination->Exists(&exists)));
+ MOZ_ASSERT(exists);
+
+ int64_t destSize = 0;
+ QM_TRY(QM_TO_RESULT(destination->GetFileSize(&destSize)));
+
+ int64_t origSize = 0;
+ QM_TRY(QM_TO_RESULT(original->GetFileSize(&origSize)));
+
+ MOZ_ASSERT(destSize == origSize);
+#endif
+
+ return destination;
+}
+
+Result<Usage, QMResult> FileSystemFileManager::RemoveFile(
+ const FileId& aFileId) {
+ MOZ_ASSERT(!aFileId.IsEmpty());
+ QM_TRY_UNWRAP(nsCOMPtr<nsIFile> pathObject,
+ GetFileDestination(mTopDirectory, aFileId));
+
+ bool exists = false;
+ QM_TRY(QM_TO_RESULT(pathObject->Exists(&exists)));
+
+ if (!exists) {
+ return 0;
+ }
+
+ bool isFile = false;
+ QM_TRY(QM_TO_RESULT(pathObject->IsFile(&isFile)));
+
+ // We could handle this also as a nonexistent file.
+ if (!isFile) {
+ return Err(QMResult(NS_ERROR_FILE_IS_DIRECTORY));
+ }
+
+ Usage totalUsage = 0;
+#ifdef DEBUG
+ QM_TRY_UNWRAP(totalUsage,
+ QM_TO_RESULT_INVOKE_MEMBER(pathObject, GetFileSize));
+#endif
+
+ QM_TRY(QM_TO_RESULT(pathObject->Remove(/* recursive */ false)));
+
+ return totalUsage;
+}
+
+Result<DebugOnly<Usage>, QMResult> FileSystemFileManager::RemoveFiles(
+ const nsTArray<FileId>& aFileIds, nsTArray<FileId>& aFailedRemovals) {
+ if (aFileIds.IsEmpty()) {
+ return DebugOnly<Usage>(0);
+ }
+
+ CheckedInt64 totalUsage = 0;
+ for (const auto& someId : aFileIds) {
+ QM_WARNONLY_TRY_UNWRAP(Maybe<nsCOMPtr<nsIFile>> maybeFile,
+ GetFileDestination(mTopDirectory, someId));
+ if (!maybeFile) {
+ aFailedRemovals.AppendElement(someId);
+ continue;
+ }
+ nsCOMPtr<nsIFile> fileObject = maybeFile.value();
+
+// Size recorded at close is checked to be equal to the sum of sizes on disk
+#ifdef DEBUG
+ QM_WARNONLY_TRY_UNWRAP(Maybe<Usage> fileSize, GetFileSize(fileObject));
+ if (!fileSize) {
+ aFailedRemovals.AppendElement(someId);
+ continue;
+ }
+ totalUsage += fileSize.value();
+#endif
+
+ QM_WARNONLY_TRY_UNWRAP(Maybe<Ok> ok,
+ MOZ_TO_RESULT(RemoveFileObject(fileObject)));
+ if (!ok) {
+ aFailedRemovals.AppendElement(someId);
+ }
+ }
+
+ MOZ_ASSERT(totalUsage.isValid());
+
+ return DebugOnly<Usage>(totalUsage.value());
+}
+
+} // 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..46391db6f7
--- /dev/null
+++ b/dom/fs/parent/datamodel/FileSystemFileManager.h
@@ -0,0 +1,175 @@
+/* -*- 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/UniquePtr.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 {
+
+struct FileId;
+
+namespace 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 quota::OriginMetadata& aOriginMetadata);
+
+/**
+ * @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 quota::OriginMetadata& aOriginMetadata);
+
+/**
+ * @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 quota::OriginMetadata& aOriginMetadata,
+ 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<UniquePtr<FileSystemFileManager>, QMResult>
+ CreateFileSystemFileManager(const quota::OriginMetadata& aOriginMetadata);
+
+ /**
+ * @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 FileId& aFileId) const;
+
+ /**
+ * @brief Get or create a disk-backed file object for a specified entry id.
+ *
+ * @param aFileId Specified id of a file system entry
+ * @return Result<nsCOMPtr<nsIFile>, QMResult> File abstraction or IO error
+ */
+ Result<nsCOMPtr<nsIFile>, QMResult> GetOrCreateFile(const FileId& aFileId);
+
+ /**
+ * @brief Create a disk-backed file object as a copy.
+ *
+ * @param aDestinationFileId Specified id of file to be created
+ * @param aSourceFileId Specified id of the file from which we make a copy
+ * @return Result<nsCOMPtr<nsIFile>, QMResult> File abstraction or IO error
+ */
+ Result<nsCOMPtr<nsIFile>, QMResult> CreateFileFrom(
+ const FileId& aDestinationFileId, const FileId& aSourceFileId);
+
+ /**
+ * @brief Remove the disk-backed file object for a specified entry id.
+ * Note: The returned value is 0 in release builds.
+ *
+ * @param aFileId Specified id of a file system entry
+ * @return Result<Usage, QMResult> Error or file size
+ */
+ Result<Usage, QMResult> RemoveFile(const FileId& aFileId);
+
+ /**
+ * @brief This method can be used to try to delete a group of files from the
+ * disk. In debug builds, the sum of the usages is provided ad return value,
+ * in release builds the sum is not calculated.
+ * The method attempts to remove all the files requested.
+ */
+ Result<DebugOnly<Usage>, QMResult> RemoveFiles(
+ const nsTArray<FileId>& aFileIds, nsTArray<FileId>& aFailedRemovals);
+
+ private:
+ explicit FileSystemFileManager(nsCOMPtr<nsIFile>&& aTopDirectory);
+
+ nsCOMPtr<nsIFile> mTopDirectory;
+};
+
+} // namespace data
+} // namespace fs
+} // 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..cf6eee72cc
--- /dev/null
+++ b/dom/fs/parent/datamodel/SchemaVersion001.cpp
@@ -0,0 +1,193 @@
+/* -*- 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 "StartedTransaction.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 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, kRootString));
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn));
+
+ {
+ 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, kRootString)));
+ QM_TRY(MOZ_TO_RESULT(stmt.Execute()));
+ }
+
+ return transaction.Commit();
+}
+
+} // namespace
+
+nsresult SetEncoding(ResultConnection& aConn) {
+ return aConn->ExecuteSimpleSQL(R"(PRAGMA encoding = "UTF-16";)"_ns);
+}
+
+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));
+
+ QM_TRY_UNWRAP(bool tablesExist, stmt.YesOrNoQuery());
+
+ return !tablesExist;
+};
+
+nsresult SchemaVersion001::CreateTables(ResultConnection& aConn,
+ const Origin& aOrigin) {
+ QM_TRY(MOZ_TO_RESULT(CreateEntries(aConn)));
+ QM_TRY(MOZ_TO_RESULT(CreateDirectories(aConn)));
+ QM_TRY(MOZ_TO_RESULT(CreateFiles(aConn)));
+ QM_TRY(MOZ_TO_RESULT(CreateUsages(aConn)));
+ QM_TRY(MOZ_TO_RESULT(CreateRootEntry(aConn, aOrigin)));
+
+ return NS_OK;
+}
+
+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(&currentVersion)));
+ }
+
+ if (currentVersion < sVersion) {
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn));
+
+ QM_TRY(QM_TO_RESULT(SchemaVersion001::CreateTables(aConn, aOrigin)));
+ QM_TRY(QM_TO_RESULT(aConn->SetSchemaVersion(sVersion)));
+
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+ }
+
+ QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
+
+ QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(&currentVersion)));
+
+ 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..72d38e4dfd
--- /dev/null
+++ b/dom/fs/parent/datamodel/SchemaVersion001.h
@@ -0,0 +1,31 @@
+/* -*- 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 nsresult CreateTables(ResultConnection& aConn, const Origin& aOrigin);
+
+ static Result<DatabaseVersion, QMResult> InitializeConnection(
+ ResultConnection& aConn, const Origin& aOrigin);
+
+ static constexpr DatabaseVersion sVersion = 1;
+};
+
+nsresult SetEncoding(ResultConnection& aConn);
+
+Result<bool, QMResult> CheckIfEmpty(ResultConnection& aConn);
+
+} // namespace mozilla::dom::fs
+
+#endif // DOM_FS_PARENT_DATAMODEL_SCHEMAVERSION001_H_
diff --git a/dom/fs/parent/datamodel/SchemaVersion002.cpp b/dom/fs/parent/datamodel/SchemaVersion002.cpp
new file mode 100644
index 0000000000..57d4736b88
--- /dev/null
+++ b/dom/fs/parent/datamodel/SchemaVersion002.cpp
@@ -0,0 +1,618 @@
+/* -*- 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 "SchemaVersion002.h"
+
+#include "FileSystemFileManager.h"
+#include "FileSystemHashSource.h"
+#include "FileSystemHashStorageFunction.h"
+#include "ResultStatement.h"
+#include "StartedTransaction.h"
+#include "fs/FileSystemConstants.h"
+#include "mozStorageHelper.h"
+#include "mozilla/dom/quota/QuotaCommon.h"
+#include "mozilla/dom/quota/ResultExtensions.h"
+#include "nsID.h"
+
+namespace mozilla::dom::fs {
+
+namespace {
+
+nsresult CreateFileIds(ResultConnection& aConn) {
+ return aConn->ExecuteSimpleSQL(
+ "CREATE TABLE IF NOT EXISTS FileIds ( "
+ "fileId BLOB PRIMARY KEY, "
+ "handle BLOB, "
+ "FOREIGN KEY (handle) "
+ "REFERENCES Files (handle) "
+ "ON DELETE SET NULL ) "
+ ";"_ns);
+}
+
+nsresult CreateMainFiles(ResultConnection& aConn) {
+ return aConn->ExecuteSimpleSQL(
+ "CREATE TABLE IF NOT EXISTS MainFiles ( "
+ "handle BLOB UNIQUE, "
+ "fileId BLOB UNIQUE, "
+ "FOREIGN KEY (handle) REFERENCES Files (handle) "
+ "ON DELETE CASCADE, "
+ "FOREIGN KEY (fileId) REFERENCES FileIds (fileId) "
+ "ON DELETE SET NULL ) "
+ ";"_ns);
+}
+
+nsresult PopulateFileIds(ResultConnection& aConn) {
+ return aConn->ExecuteSimpleSQL(
+ "INSERT OR IGNORE INTO FileIds ( fileId, handle ) "
+ "SELECT handle, handle FROM Files "
+ ";"_ns);
+}
+
+nsresult PopulateMainFiles(ResultConnection& aConn) {
+ return aConn->ExecuteSimpleSQL(
+ "INSERT OR IGNORE INTO MainFiles ( fileId, handle ) "
+ "SELECT handle, handle FROM Files "
+ ";"_ns);
+}
+
+Result<Ok, QMResult> ClearInvalidFileIds(
+ ResultConnection& aConn, data::FileSystemFileManager& aFileManager) {
+ // We cant't just clear all file ids because if a file was accessed using
+ // writable file stream a new file id was created which is not the same as
+ // entry id.
+
+ // Get all file ids first.
+ QM_TRY_INSPECT(
+ const auto& allFileIds,
+ ([&aConn]() -> Result<nsTArray<FileId>, QMResult> {
+ const nsLiteralCString allFileIdsQuery =
+ "SELECT fileId FROM FileIds;"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConn, allFileIdsQuery));
+
+ nsTArray<FileId> fileIds;
+
+ while (true) {
+ QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
+ if (!moreResults) {
+ break;
+ }
+
+ QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 0u));
+
+ fileIds.AppendElement(fileId);
+ }
+
+ return std::move(fileIds);
+ }()));
+
+ // Filter out file ids which have non-zero-sized files on disk.
+ QM_TRY_INSPECT(const auto& invalidFileIds,
+ ([&aFileManager](const nsTArray<FileId>& aFileIds)
+ -> Result<nsTArray<FileId>, QMResult> {
+ nsTArray<FileId> fileIds;
+
+ for (const auto& fileId : aFileIds) {
+ QM_TRY_UNWRAP(auto file, aFileManager.GetFile(fileId));
+
+ QM_TRY_INSPECT(const bool& exists,
+ QM_TO_RESULT_INVOKE_MEMBER(file, Exists));
+
+ if (exists) {
+ QM_TRY_INSPECT(
+ const int64_t& fileSize,
+ QM_TO_RESULT_INVOKE_MEMBER(file, GetFileSize));
+
+ if (fileSize != 0) {
+ continue;
+ }
+
+ QM_TRY(QM_TO_RESULT(file->Remove(false)));
+ }
+
+ fileIds.AppendElement(fileId);
+ }
+
+ return std::move(fileIds);
+ }(allFileIds)));
+
+ // Finally, clear invalid file ids.
+ QM_TRY(([&aConn](const nsTArray<FileId>& aFileIds) -> Result<Ok, QMResult> {
+ for (const auto& fileId : aFileIds) {
+ const nsLiteralCString clearFileIdsQuery =
+ "DELETE FROM FileIds "
+ "WHERE fileId = :fileId "
+ ";"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConn, clearFileIdsQuery));
+
+ QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, fileId)));
+
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ return Ok{};
+ }(invalidFileIds)));
+
+ return Ok{};
+}
+
+Result<Ok, QMResult> ClearInvalidMainFiles(
+ ResultConnection& aConn, data::FileSystemFileManager& aFileManager) {
+ // We cant't just clear all main files because if a file was accessed using
+ // writable file stream a new main file was created which is not the same as
+ // entry id.
+
+ // Get all main files first.
+ QM_TRY_INSPECT(
+ const auto& allMainFiles,
+ ([&aConn]() -> Result<nsTArray<std::pair<EntryId, FileId>>, QMResult> {
+ const nsLiteralCString allMainFilesQuery =
+ "SELECT handle, fileId FROM MainFiles;"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConn, allMainFilesQuery));
+
+ nsTArray<std::pair<EntryId, FileId>> mainFiles;
+
+ while (true) {
+ QM_TRY_UNWRAP(bool moreResults, stmt.ExecuteStep());
+ if (!moreResults) {
+ break;
+ }
+
+ QM_TRY_UNWRAP(EntryId entryId,
+ stmt.GetEntryIdByColumn(/* Column */ 0u));
+ QM_TRY_UNWRAP(FileId fileId, stmt.GetFileIdByColumn(/* Column */ 1u));
+
+ mainFiles.AppendElement(std::pair<EntryId, FileId>(entryId, fileId));
+ }
+
+ return std::move(mainFiles);
+ }()));
+
+ // Filter out main files which have non-zero-sized files on disk.
+ QM_TRY_INSPECT(
+ const auto& invalidMainFiles,
+ ([&aFileManager](const nsTArray<std::pair<EntryId, FileId>>& aMainFiles)
+ -> Result<nsTArray<std::pair<EntryId, FileId>>, QMResult> {
+ nsTArray<std::pair<EntryId, FileId>> mainFiles;
+
+ for (const auto& mainFile : aMainFiles) {
+ QM_TRY_UNWRAP(auto file, aFileManager.GetFile(mainFile.second));
+
+ QM_TRY_INSPECT(const bool& exists,
+ QM_TO_RESULT_INVOKE_MEMBER(file, Exists));
+
+ if (exists) {
+ QM_TRY_INSPECT(const int64_t& fileSize,
+ QM_TO_RESULT_INVOKE_MEMBER(file, GetFileSize));
+
+ if (fileSize != 0) {
+ continue;
+ }
+
+ QM_TRY(QM_TO_RESULT(file->Remove(false)));
+ }
+
+ mainFiles.AppendElement(mainFile);
+ }
+
+ return std::move(mainFiles);
+ }(allMainFiles)));
+
+ // Finally, clear invalid main files.
+ QM_TRY(([&aConn](const nsTArray<std::pair<EntryId, FileId>>& aMainFiles)
+ -> Result<Ok, QMResult> {
+ for (const auto& mainFile : aMainFiles) {
+ const nsLiteralCString clearMainFilesQuery =
+ "DELETE FROM MainFiles "
+ "WHERE handle = :entryId AND fileId = :fileId "
+ ";"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConn, clearMainFilesQuery));
+
+ QM_TRY(
+ QM_TO_RESULT(stmt.BindEntryIdByName("entryId"_ns, mainFile.first)));
+ QM_TRY(QM_TO_RESULT(stmt.BindFileIdByName("fileId"_ns, mainFile.second)));
+
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ return Ok{};
+ }(invalidMainFiles)));
+
+ return Ok{};
+}
+
+nsresult ConnectUsagesToFileIds(ResultConnection& aConn) {
+ QM_TRY(
+ MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = OFF;"_ns)));
+
+ auto turnForeignKeysBackOn = MakeScopeExit([&aConn]() {
+ QM_WARNONLY_TRY(
+ MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
+ });
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn));
+
+ QM_TRY(MOZ_TO_RESULT(
+ aConn->ExecuteSimpleSQL("DROP TABLE IF EXISTS migrateUsages ;"_ns)));
+
+ QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL(
+ "CREATE TABLE migrateUsages ( "
+ "handle BLOB PRIMARY KEY, "
+ "usage INTEGER NOT NULL DEFAULT 0, "
+ "tracked BOOLEAN NOT NULL DEFAULT 0 CHECK (tracked IN (0, 1)), "
+ "CONSTRAINT handles_are_fileIds "
+ "FOREIGN KEY (handle) "
+ "REFERENCES FileIds (fileId) "
+ "ON DELETE CASCADE ) "
+ ";"_ns)));
+
+ QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL(
+ "INSERT INTO migrateUsages ( handle, usage, tracked ) "
+ "SELECT handle, usage, tracked FROM Usages ;"_ns)));
+
+ QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("DROP TABLE Usages;"_ns)));
+
+ QM_TRY(MOZ_TO_RESULT(aConn->ExecuteSimpleSQL(
+ "ALTER TABLE migrateUsages RENAME TO Usages;"_ns)));
+
+ QM_TRY(
+ MOZ_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_key_check;"_ns)));
+
+ QM_TRY(MOZ_TO_RESULT(transaction.Commit()));
+
+ return NS_OK;
+}
+
+nsresult CreateEntryNamesView(ResultConnection& aConn) {
+ return aConn->ExecuteSimpleSQL(
+ "CREATE VIEW IF NOT EXISTS EntryNames AS "
+ "SELECT isFile, handle, parent, name FROM Entries INNER JOIN ( "
+ "SELECT 1 AS isFile, handle, name FROM Files UNION "
+ "SELECT 0, handle, name FROM Directories ) "
+ "USING (handle) "
+ ";"_ns);
+}
+
+nsresult FixEntryIds(const ResultConnection& aConnection,
+ const EntryId& aRootEntry) {
+ const nsLiteralCString calculateHashesQuery =
+ "CREATE TEMPORARY TABLE EntryMigrationTable AS "
+ "WITH RECURSIVE "
+ "rehashMap( depth, isFile, handle, parent, name, hash ) AS ( "
+ "SELECT 0, isFile, handle, parent, name, hashEntry( :rootEntry, name ) "
+ "FROM EntryNames WHERE parent = :rootEntry UNION SELECT "
+ "1 + depth, EntryNames.isFile, EntryNames.handle, EntryNames.parent, "
+ "EntryNames.name, hashEntry( rehashMap.hash, EntryNames.name ) "
+ "FROM rehashMap, EntryNames WHERE rehashMap.handle = EntryNames.parent ) "
+ "SELECT depth, isFile, handle, parent, name, hash FROM rehashMap "
+ ";"_ns;
+
+ const nsLiteralCString createIndexByDepthQuery =
+ "CREATE INDEX indexOnDepth ON EntryMigrationTable ( depth ); "_ns;
+
+ // To avoid constraint violation, new entries are inserted under a temporary
+ // parent.
+
+ const nsLiteralCString insertTemporaryParentEntry =
+ "INSERT INTO Entries ( handle, parent ) "
+ "VALUES ( :tempParent, :rootEntry ) ;"_ns;
+
+ const nsLiteralCString flagTemporaryParentAsDir =
+ "INSERT INTO Directories ( handle, name ) "
+ "VALUES ( :tempParent, 'temp' ) ;"_ns;
+
+ const nsLiteralCString insertNewEntriesQuery =
+ "INSERT INTO Entries ( handle, parent ) "
+ "SELECT hash, :tempParent FROM EntryMigrationTable WHERE hash != handle "
+ ";"_ns;
+
+ const nsLiteralCString insertNewDirectoriesQuery =
+ "INSERT INTO Directories ( handle, name ) "
+ "SELECT hash, name FROM EntryMigrationTable "
+ "WHERE isFile = 0 AND hash != handle "
+ "ORDER BY depth "
+ ";"_ns;
+
+ const nsLiteralCString insertNewFilesQuery =
+ "INSERT INTO Files ( handle, type, name ) "
+ "SELECT EntryMigrationTable.hash, Files.type, EntryMigrationTable.name "
+ "FROM EntryMigrationTable INNER JOIN Files USING (handle) "
+ "WHERE EntryMigrationTable.isFile = 1 AND hash != handle "
+ ";"_ns;
+
+ const nsLiteralCString updateFileMappingsQuery =
+ "UPDATE FileIds SET handle = hash "
+ "FROM ( SELECT handle, hash FROM EntryMigrationTable WHERE hash != "
+ "handle ) "
+ "AS replacement WHERE FileIds.handle = replacement.handle "
+ ";"_ns;
+
+ const nsLiteralCString updateMainFilesQuery =
+ "UPDATE MainFiles SET handle = hash "
+ "FROM ( SELECT handle, hash FROM EntryMigrationTable WHERE hash != "
+ "handle ) "
+ "AS replacement WHERE MainFiles.handle = replacement.handle "
+ ";"_ns;
+
+ // Now fix the parents.
+ const nsLiteralCString updateEntryMappingsQuery =
+ "UPDATE Entries SET parent = hash "
+ "FROM ( SELECT Lhs.hash AS handle, Rhs.hash AS hash, Lhs.depth AS depth "
+ "FROM EntryMigrationTable AS Lhs "
+ "INNER JOIN EntryMigrationTable AS Rhs "
+ "ON Rhs.handle = Lhs.parent ORDER BY depth ) AS replacement "
+ "WHERE Entries.handle = replacement.handle "
+ "AND Entries.parent = :tempParent "
+ ";"_ns;
+
+ const nsLiteralCString cleanupOldEntriesQuery =
+ "DELETE FROM Entries WHERE handle IN "
+ "( SELECT handle FROM EntryMigrationTable WHERE hash != handle ) "
+ ";"_ns;
+
+ const nsLiteralCString cleanupTemporaryParent =
+ "DELETE FROM Entries WHERE handle = :tempParent ;"_ns;
+
+ const nsLiteralCString dropIndexByDepthQuery =
+ "DROP INDEX indexOnDepth ; "_ns;
+
+ // Index is automatically deleted
+ const nsLiteralCString cleanupTemporaries =
+ "DROP TABLE EntryMigrationTable ;"_ns;
+
+ EntryId tempParent(nsCString(nsID::GenerateUUID().ToString().get()));
+
+ nsCOMPtr<mozIStorageFunction> rehashFunction =
+ new data::FileSystemHashStorageFunction();
+ QM_TRY(MOZ_TO_RESULT(aConnection->CreateFunction("hashEntry"_ns,
+ /* number of arguments */ 2,
+ rehashFunction)));
+ auto finallyRemoveFunction = MakeScopeExit([&aConnection]() {
+ QM_WARNONLY_TRY(MOZ_TO_RESULT(aConnection->RemoveFunction("hashEntry"_ns)));
+ });
+
+ // We need this to make sure the old entries get removed
+ QM_TRY(MOZ_TO_RESULT(
+ aConnection->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConnection));
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, calculateHashesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("rootEntry"_ns, aRootEntry)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(createIndexByDepthQuery)));
+
+ {
+ QM_TRY_UNWRAP(
+ ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertTemporaryParentEntry));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("rootEntry"_ns, aRootEntry)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(
+ ResultStatement stmt,
+ ResultStatement::Create(aConnection, flagTemporaryParentAsDir));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertNewEntriesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(
+ ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertNewDirectoriesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, insertNewFilesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(
+ ResultStatement stmt,
+ ResultStatement::Create(aConnection, updateFileMappingsQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, updateMainFilesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(
+ ResultStatement stmt,
+ ResultStatement::Create(aConnection, updateEntryMappingsQuery));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, cleanupOldEntriesQuery));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, cleanupTemporaryParent));
+ QM_TRY(QM_TO_RESULT(stmt.BindEntryIdByName("tempParent"_ns, tempParent)));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ QM_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL(dropIndexByDepthQuery)));
+
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConnection, cleanupTemporaries));
+ QM_TRY(QM_TO_RESULT(stmt.Execute()));
+ }
+
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+
+ QM_WARNONLY_TRY(QM_TO_RESULT(aConnection->ExecuteSimpleSQL("VACUUM;"_ns)));
+
+ return NS_OK;
+}
+
+} // namespace
+
+Result<DatabaseVersion, QMResult> SchemaVersion002::InitializeConnection(
+ ResultConnection& aConn, data::FileSystemFileManager& aFileManager,
+ const Origin& aOrigin) {
+ QM_TRY_UNWRAP(const bool wasEmpty, CheckIfEmpty(aConn));
+
+ DatabaseVersion currentVersion = 0;
+
+ if (wasEmpty) {
+ QM_TRY(QM_TO_RESULT(SetEncoding(aConn)));
+ } else {
+ QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(&currentVersion)));
+ }
+
+ if (currentVersion < sVersion) {
+ MOZ_ASSERT_IF(0 != currentVersion, 1 == currentVersion);
+
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn));
+
+ if (0 == currentVersion) {
+ QM_TRY(QM_TO_RESULT(SchemaVersion001::CreateTables(aConn, aOrigin)));
+ }
+
+ QM_TRY(QM_TO_RESULT(CreateFileIds(aConn)));
+
+ if (!wasEmpty) {
+ QM_TRY(QM_TO_RESULT(PopulateFileIds(aConn)));
+ }
+
+ QM_TRY(QM_TO_RESULT(ConnectUsagesToFileIds(aConn)));
+
+ QM_TRY(QM_TO_RESULT(CreateMainFiles(aConn)));
+ if (!wasEmpty) {
+ QM_TRY(QM_TO_RESULT(PopulateMainFiles(aConn)));
+ }
+
+ QM_TRY(QM_TO_RESULT(CreateEntryNamesView(aConn)));
+
+ QM_TRY(QM_TO_RESULT(aConn->SetSchemaVersion(sVersion)));
+
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+
+ if (!wasEmpty) {
+ QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("VACUUM;"_ns)));
+ }
+ }
+
+ // The upgrade from version 1 to version 2 was buggy, so we have to check if
+ // the Usages table still references the Files table which is a sign that
+ // the upgrade wasn't complete. This extra query has only negligible perf
+ // impact. See bug 1847989.
+ auto UsagesTableRefsFilesTable = [&aConn]() -> Result<bool, QMResult> {
+ const nsLiteralCString query =
+ "SELECT pragma_foreign_key_list.'table'=='Files' "
+ "FROM pragma_foreign_key_list('Usages');"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt, ResultStatement::Create(aConn, query));
+
+ return stmt.YesOrNoQuery();
+ };
+
+ QM_TRY_UNWRAP(auto usagesTableRefsFilesTable, UsagesTableRefsFilesTable());
+
+ if (usagesTableRefsFilesTable) {
+ QM_TRY_UNWRAP(auto transaction, StartedTransaction::Create(aConn));
+
+ // The buggy upgrade didn't call PopulateFileIds, ConnectUsagesToFileIds
+ // and PopulateMainFiles was completely missing. Since invalid file ids
+ // and main files could be inserted when the profile was broken, we need
+ // to clear them before populating.
+ QM_TRY(ClearInvalidFileIds(aConn, aFileManager));
+ QM_TRY(QM_TO_RESULT(PopulateFileIds(aConn)));
+ QM_TRY(QM_TO_RESULT(ConnectUsagesToFileIds(aConn)));
+ QM_TRY(ClearInvalidMainFiles(aConn, aFileManager));
+ QM_TRY(QM_TO_RESULT(PopulateMainFiles(aConn)));
+
+ QM_TRY(QM_TO_RESULT(transaction.Commit()));
+
+ QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("VACUUM;"_ns)));
+
+ QM_TRY_UNWRAP(usagesTableRefsFilesTable, UsagesTableRefsFilesTable());
+ MOZ_ASSERT(!usagesTableRefsFilesTable);
+ }
+
+ // In schema version 001, entryId was unique but not necessarily related to
+ // a path. For schema 002, we have to fix all entryIds to be derived from
+ // the underlying path.
+ auto OneTimeRehashingDone = [&aConn]() -> Result<bool, QMResult> {
+ const nsLiteralCString query =
+ "SELECT EXISTS (SELECT 1 FROM sqlite_master "
+ "WHERE type='table' AND name='RehashedFrom001to002' ) ;"_ns;
+
+ QM_TRY_UNWRAP(ResultStatement stmt, ResultStatement::Create(aConn, query));
+
+ return stmt.YesOrNoQuery();
+ };
+
+ QM_TRY_UNWRAP(auto oneTimeRehashingDone, OneTimeRehashingDone());
+
+ if (!oneTimeRehashingDone) {
+ const nsLiteralCString findRootEntry =
+ "SELECT handle FROM Entries WHERE parent IS NULL ;"_ns;
+
+ EntryId rootId;
+ {
+ QM_TRY_UNWRAP(ResultStatement stmt,
+ ResultStatement::Create(aConn, findRootEntry));
+
+ QM_TRY_UNWRAP(DebugOnly<bool> moreResults, stmt.ExecuteStep());
+ MOZ_ASSERT(moreResults);
+
+ QM_TRY_UNWRAP(rootId, stmt.GetEntryIdByColumn(/* Column */ 0u));
+ }
+
+ MOZ_ASSERT(!rootId.IsEmpty());
+
+ QM_TRY(QM_TO_RESULT(FixEntryIds(aConn, rootId)));
+
+ QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL(
+ "CREATE TABLE RehashedFrom001to002 (id INTEGER PRIMARY KEY);"_ns)));
+
+ QM_TRY_UNWRAP(DebugOnly<bool> isDoneNow, OneTimeRehashingDone());
+ MOZ_ASSERT(isDoneNow);
+ }
+
+ QM_TRY(QM_TO_RESULT(aConn->ExecuteSimpleSQL("PRAGMA foreign_keys = ON;"_ns)));
+
+ QM_TRY(QM_TO_RESULT(aConn->GetSchemaVersion(&currentVersion)));
+
+ return currentVersion;
+}
+
+} // namespace mozilla::dom::fs
diff --git a/dom/fs/parent/datamodel/SchemaVersion002.h b/dom/fs/parent/datamodel/SchemaVersion002.h
new file mode 100644
index 0000000000..60f2aa53cb
--- /dev/null
+++ b/dom/fs/parent/datamodel/SchemaVersion002.h
@@ -0,0 +1,28 @@
+/* -*- 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_SCHEMAVERSION002_H_
+#define DOM_FS_PARENT_DATAMODEL_SCHEMAVERSION002_H_
+
+#include "SchemaVersion001.h"
+
+namespace mozilla::dom::fs {
+
+namespace data {
+class FileSystemFileManager;
+} // namespace data
+
+struct SchemaVersion002 {
+ static Result<DatabaseVersion, QMResult> InitializeConnection(
+ ResultConnection& aConn, data::FileSystemFileManager& aFileManager,
+ const Origin& aOrigin);
+
+ static constexpr DatabaseVersion sVersion = 2;
+};
+
+} // namespace mozilla::dom::fs
+
+#endif // DOM_FS_PARENT_DATAMODEL_SCHEMAVERSION002_H_
diff --git a/dom/fs/parent/datamodel/moz.build b/dom/fs/parent/datamodel/moz.build
new file mode 100644
index 0000000000..8bb5d35381
--- /dev/null
+++ b/dom/fs/parent/datamodel/moz.build
@@ -0,0 +1,28 @@
+# -*- 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",
+ "FileSystemDatabaseManagerVersion002.cpp",
+ "FileSystemDataManager.cpp",
+ "FileSystemFileManager.cpp",
+ "SchemaVersion001.cpp",
+ "SchemaVersion002.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..95298ad0f2
--- /dev/null
+++ b/dom/fs/parent/moz.build
@@ -0,0 +1,63 @@
+# -*- 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 += [
+ "FileSystemAccessHandle.h",
+ "FileSystemAccessHandleControlParent.h",
+ "FileSystemAccessHandleParent.h",
+ "FileSystemManagerParent.h",
+ "FileSystemManagerParentFactory.h",
+ "FileSystemParentTypes.h",
+ "FileSystemQuotaClient.h",
+ "FileSystemQuotaClientFactory.h",
+ "FileSystemWritableFileStreamParent.h",
+]
+
+UNIFIED_SOURCES += [
+ "FileSystemAccessHandle.cpp",
+ "FileSystemAccessHandleControlParent.cpp",
+ "FileSystemAccessHandleParent.cpp",
+ "FileSystemContentTypeGuess.cpp",
+ "FileSystemHashSource.cpp",
+ "FileSystemHashStorageFunction.cpp",
+ "FileSystemManagerParent.cpp",
+ "FileSystemManagerParentFactory.cpp",
+ "FileSystemQuotaClient.cpp",
+ "FileSystemQuotaClientFactory.cpp",
+ "FileSystemStreamCallbacks.cpp",
+ "FileSystemWritableFileStreamParent.cpp",
+ "ResultStatement.cpp",
+ "StartedTransaction.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"],
+ )
+
+ CbindgenHeader(
+ "mime_guess_ffi_generated.h",
+ inputs=["/dom/fs/parent/rust/mime-guess-ffi"],
+ )
+
+ EXPORTS.mozilla.dom += [
+ "!data_encoding_ffi_generated.h",
+ "!mime_guess_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[..]));
+}
diff --git a/dom/fs/parent/rust/mime-guess-ffi/Cargo.toml b/dom/fs/parent/rust/mime-guess-ffi/Cargo.toml
new file mode 100644
index 0000000000..d97f8202d7
--- /dev/null
+++ b/dom/fs/parent/rust/mime-guess-ffi/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "mime-guess-ffi"
+version = "0.1.0"
+license = "MPL-2.0"
+authors = ["The Mozilla Project Developers"]
+
+[dependencies]
+mime_guess = "2.0.4"
+nserror = { path = "../../../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../../../xpcom/rust/nsstring" }
diff --git a/dom/fs/parent/rust/mime-guess-ffi/cbindgen.toml b/dom/fs/parent/rust/mime-guess-ffi/cbindgen.toml
new file mode 100644
index 0000000000..d7f94927ab
--- /dev/null
+++ b/dom/fs/parent/rust/mime-guess-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_MIME_GUESS_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/mime-guess-ffi/src/lib.rs b/dom/fs/parent/rust/mime-guess-ffi/src/lib.rs
new file mode 100644
index 0000000000..19833fd202
--- /dev/null
+++ b/dom/fs/parent/rust/mime-guess-ffi/src/lib.rs
@@ -0,0 +1,34 @@
+/* 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 mime_guess;
+extern crate nserror;
+extern crate nsstring;
+
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_ERROR_INVALID_ARG, NS_ERROR_NOT_AVAILABLE, NS_OK};
+use nsstring::{nsACString, nsCString};
+use std::path::Path;
+use std::str;
+
+#[no_mangle]
+pub extern "C" fn mimeGuessFromPath(path: &nsACString, content_type: &mut nsCString) -> nsresult {
+ let path_data = str::from_utf8(path.as_ref());
+ if path_data.is_err() {
+ return NS_ERROR_INVALID_ARG; // Not UTF8
+ }
+
+ let content_path = Path::new(path_data.unwrap());
+ if content_path.extension().is_none() {
+ return NS_ERROR_NOT_AVAILABLE; // No mime type information
+ }
+
+ let maybe_mime = mime_guess::from_path(content_path).first_raw();
+ if maybe_mime.is_none() {
+ return NS_ERROR_FAILURE; // Not recognized
+ }
+
+ content_type.assign(maybe_mime.unwrap());
+
+ NS_OK
+}