| // Copyright 2024 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #ifndef CHROME_BROWSER_ASH_FILEAPI_DIVERSION_FILE_MANAGER_H_ |
| #define CHROME_BROWSER_ASH_FILEAPI_DIVERSION_FILE_MANAGER_H_ |
| |
| #include <map> |
| #include <memory> |
| |
| #include "base/files/file.h" |
| #include "base/files/file_path.h" |
| #include "base/functional/callback_forward.h" |
| #include "storage/browser/file_system/file_stream_reader.h" |
| #include "storage/browser/file_system/file_stream_writer.h" |
| #include "storage/browser/file_system/file_system_operation.h" |
| #include "storage/browser/file_system/file_system_url.h" |
| |
| namespace ash { |
| |
| // Manages the creation, destruction and access to Diversion Files. |
| // |
| // Chromium's SBFS (//storage/browser/file_system) code implements an |
| // in-process virtual file system. It presents a traditional, POSIX-like API |
| // for a block-based file model (e.g. "open; write; write; write; close" or, |
| // when executing untrusted third-party code where file descriptor Denial of |
| // Service is a concern, "owc; owc; owc", where "owc" is a combined "open; |
| // write; close") where writes can be incremental and appending to an existing |
| // file is assumed to have O(1) complexity, where N is the prior file size. |
| // |
| // Some SBFS backends are backed by 'the cloud' instead of by local disk, and |
| // may be document-based (centered on a one-shot download/upload API) instead |
| // of block-based (open, read, write and close). In particular, appending M |
| // bytes to a virtual file (of size N) may first require downloading N bytes |
| // and then uploading (N + M) bytes, which has O(N) complexity for a single |
| // write operation. Overall, with a naive implementation, workflows with |
| // multiple write ops may require a quadratic amount of time and network. |
| // |
| // A Diversion File is a local file (with "O(1) append" behavior) that proxies |
| // or caches these remote files. For read-only workflows, this is basically |
| // just a local cache. For read-write workflows, writes are diverted to this |
| // file before ultimately being uploaded to the remote database, and follow-up |
| // reads (e.g. calculating Safe Browsing hashes of freshly written files) can |
| // hit the faster local disk instead of the slower remote database. |
| // |
| // Diversion (for a specific FileSystemURL) starts with a StartDiverting call |
| // and stops either explicitly or implicitly. Explicitly means after a |
| // FinishDiverting call. Implicitly means after an idle_timeout amount of time |
| // has passed since there were no live (constructed but not yet destroyed) |
| // FileStreamReader or FileStreamWriter objects for that FileSystemURL (and |
| // FinishDiverting has still not been called). At most one of explicit_callback |
| // and implicit_callback will be run. |
| // |
| // A DiversionFileManager's methods should only be called from the |
| // content::BrowserThread::IO thread. Callbacks run on the same thread. |
| class DiversionFileManager : public base::RefCounted<DiversionFileManager> { |
| public: |
| enum class StoppedReason { |
| kExplicitFinish, |
| kImplicitIdle, |
| }; |
| |
| enum class StartDivertingResult { |
| kOK, // Returned when IsDiverting was false. |
| kWasAlreadyDiverted, // Returned when IsDiverting was true. |
| }; |
| |
| enum class FinishDivertingResult { |
| kOK, // Returned when IsDiverting was true. |
| kWasNotDiverting, // Returned when IsDiverting was false. |
| }; |
| |
| // Presents the cached contents as a ScopedFD to a real (in that it's a |
| // kernel-visible file, not an SBFS virtual file), temporary file. |
| // |
| // If scoped_fd.is_valid() then it will have zero offset (in the "lseek(fd, |
| // 0, SEEK_CUR)" sense). |
| using Callback = base::OnceCallback<void(StoppedReason stopped_reason, |
| const storage::FileSystemURL& url, |
| base::ScopedFD scoped_fd, |
| int64_t file_size, |
| base::File::Error error)>; |
| |
| DiversionFileManager(); |
| |
| DiversionFileManager(const DiversionFileManager&) = delete; |
| DiversionFileManager& operator=(const DiversionFileManager&) = delete; |
| |
| bool IsDiverting(const storage::FileSystemURL& url); |
| |
| // Has no effect (and will not return kOK) if IsDiverting(url) was true. "No |
| // effect" means that the callback will not be run. |
| // |
| // The implicit_callback may be null, equivalent to an empty-body Callback (a |
| // no-op other than running the base::ScopedFD destructor; the temporary |
| // file's contents are discarded). |
| StartDivertingResult StartDiverting(const storage::FileSystemURL& url, |
| base::TimeDelta idle_timeout, |
| Callback implicit_callback); |
| |
| // Has no effect (and will not return kOK) if IsDiverting(url) was false. "No |
| // effect" means that the callback will not be run. |
| // |
| // The explicit_callback may be null, equivalent to an empty-body Callback (a |
| // no-op other than running the base::ScopedFD destructor; the temporary |
| // file's contents are discarded). |
| // |
| // If IsDiverting(url) returned true, immediately before FinishDiverting was |
| // called, then it will now return false, immediately afterwards. Regardless, |
| // the explicit_callback might not run straight away, as it only runs after |
| // all of the existing readers and writers are destroyed. |
| // |
| // If StartDiverting is called (with an equivalent url) after FinishDiverting |
| // returns but before the explicit_callback runs, then it starts a new, |
| // independent Diversion File. Subsequent reader or writer activity will not |
| // affect the file contents or file size seen by explicit_callback. |
| FinishDivertingResult FinishDiverting(const storage::FileSystemURL& url, |
| Callback explicit_callback); |
| |
| // Factory methods for objects that allow reading from or writing to |
| // Diversion Files. They return nullptr when IsDiverting(url) is false. |
| std::unique_ptr<storage::FileStreamReader> CreateDivertedFileStreamReader( |
| const storage::FileSystemURL& url, |
| int64_t offset); |
| std::unique_ptr<storage::FileStreamWriter> CreateDivertedFileStreamWriter( |
| const storage::FileSystemURL& url, |
| int64_t offset); |
| |
| void GetDivertedFileInfo( |
| const storage::FileSystemURL& url, |
| storage::FileSystemOperation::GetMetadataFieldSet fields, |
| base::OnceCallback<void(base::File::Error result, |
| const base::File::Info& file_info)> callback); |
| |
| void TruncateDivertedFile( |
| const storage::FileSystemURL& url, |
| int64_t length, |
| base::OnceCallback<void(base::File::Error result)> callback); |
| |
| void OverrideTmpfileDirForTesting(const base::FilePath& tmpfile_dir); |
| |
| private: |
| class Entry; |
| class Worker; |
| using Map = std::map<storage::FileSystemURL, |
| scoped_refptr<Entry>, |
| storage::FileSystemURL::Comparator>; |
| |
| friend class base::RefCounted<DiversionFileManager>; |
| ~DiversionFileManager(); |
| |
| std::string TmpfileDirAsString() const; |
| |
| Map entries_; |
| base::FilePath tmpfile_dir_; |
| }; |
| |
| } // namespace ash |
| |
| #endif // CHROME_BROWSER_ASH_FILEAPI_DIVERSION_FILE_MANAGER_H_ |