| // Copyright 2022 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_FUSEBOX_FUSEBOX_SERVER_H_ |
| #define CHROME_BROWSER_ASH_FUSEBOX_FUSEBOX_SERVER_H_ |
| |
| #include <string> |
| |
| #include "base/containers/circular_deque.h" |
| #include "base/files/file.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/callback_forward.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/threading/sequence_bound.h" |
| #include "base/values.h" |
| #include "chrome/browser/ash/fusebox/fusebox.pb.h" |
| #include "chrome/browser/ash/fusebox/fusebox_moniker.h" |
| #include "chrome/browser/ash/system_web_apps/apps/files_internals_debug_json_provider.h" |
| #include "storage/browser/file_system/async_file_util.h" |
| #include "storage/browser/file_system/file_system_context.h" |
| #include "third_party/abseil-cpp/absl/types/variant.h" |
| |
| class Profile; |
| |
| namespace fusebox { |
| |
| class ReadWriter; |
| |
| class Server : public ash::FilesInternalsDebugJSONProvider { |
| public: |
| struct Delegate { |
| // These methods cause D-Bus signals to be sent that a storage unit (as |
| // named by the "subdir" in "/media/fuse/fusebox/subdir") has been attached |
| // or detached. |
| virtual void OnRegisterFSURLPrefix(const std::string& subdir) = 0; |
| virtual void OnUnregisterFSURLPrefix(const std::string& subdir) = 0; |
| }; |
| |
| // Returns a pointer to the global Server instance. |
| static Server* GetInstance(); |
| |
| // Returns POSIX style (S_IFREG | rwxr-x---) bits. |
| static uint32_t MakeModeBits(bool is_directory, bool read_only); |
| |
| // The delegate should live longer than the server. |
| explicit Server(Delegate* delegate); |
| Server(const Server&) = delete; |
| Server& operator=(const Server&) = delete; |
| ~Server() override; |
| |
| // Manages monikers in the context of the Server's MonikerMap. |
| fusebox::Moniker CreateMoniker(const storage::FileSystemURL& target, |
| bool read_only); |
| void DestroyMoniker(fusebox::Moniker moniker); |
| |
| void RegisterFSURLPrefix(const std::string& subdir, |
| const std::string& fs_url_prefix, |
| bool read_only); |
| void UnregisterFSURLPrefix(const std::string& subdir); |
| |
| // Converts a FuseBox filename (e.g. "/media/fuse/fusebox/subdir/p/q.txt") to |
| // a storage::FileSystemURL, substituting the fs_url_prefix for "/etc/subdir" |
| // according to previous RegisterFSURLPrefix calls. The "/p/q.txt" suffix may |
| // be empty but "subdir" (and everything prior) must be present. |
| // |
| // If "subdir" mapped to "filesystem:origin/external/mount_name/xxx/yyy" then |
| // this returns "filesystem:origin/external/mount_name/xxx/yyy/p/q.txt" in |
| // storage::FileSystemURL form. |
| // |
| // It returns an invalid storage::FileSystemURL if the filename doesn't match |
| // "/media/fuse/fusebox/subdir/etc" or the "subdir" wasn't registered. |
| storage::FileSystemURL ResolveFilename(Profile* profile, |
| const std::string& filename); |
| |
| // Performs the inverse of ResolveFilename. It converts a FileSystemURL like |
| // "filesystem:origin/external/mount_name/xxx/yyy/p/q.txt" to a FuseBox |
| // filename like "/media/fuse/fusebox/subdir/p/q.txt". |
| // |
| // It returns an empty base::FilePath on failure, such as when there was no |
| // previously registered (subdir, fs_url_prefix) that matched. |
| base::FilePath InverseResolveFSURL(const storage::FileSystemURL& fs_url); |
| |
| // Chains GetInstance and InverseResolveFSURL, returning an empty |
| // base::FilePath when there is no instance. |
| static base::FilePath SubstituteFuseboxFilePath( |
| const storage::FileSystemURL& fs_url) { |
| Server* server = GetInstance(); |
| return server ? server->InverseResolveFSURL(fs_url) : base::FilePath(); |
| } |
| |
| // ash::FilesInternalsDebugJSONProvider overrides. |
| void GetDebugJSONForKey( |
| std::string_view key, |
| base::OnceCallback<void(JSONKeyValuePair)> callback) override; |
| |
| // These methods map 1:1 to the D-Bus methods implemented by |
| // fusebox_service_provider.cc. |
| // |
| // For the "file operation D-Bus methods" (below until "Meta D-Bus methods") |
| // in terms of semantics, they're roughly equivalent to the C standard |
| // library functions of the same name. For example, the Stat method here |
| // corresponds to the standard stat function described by "man 2 stat". |
| // |
| // These methods all take a protobuf argument and return (via a callback) |
| // another protobuf. Many of the request protos have a string-typed |
| // file_system_url field, roughly equivalent to a POSIX filename for a file |
| // or directory. These used to be full storage::FileSystemURL strings (e.g. |
| // "filesystem:chrome://file-manager/external/foo/com.bar/baz/p/q.txt") but |
| // today look like "subdir/p/q.txt". The PrefixMap is used to resolve the |
| // "subdir" prefix to recreate the storage::FileSystemURL. |
| // |
| // See system_api/dbus/fusebox/fusebox.proto for more commentary. |
| |
| // Close2 closes a virtual file opened by Open2. |
| using Close2Callback = |
| base::OnceCallback<void(const Close2ResponseProto& response)>; |
| void Close2(const Close2RequestProto& request, Close2Callback callback); |
| |
| // Create creates a file (not a directory). |
| using CreateCallback = |
| base::OnceCallback<void(const CreateResponseProto& response)>; |
| void Create(const CreateRequestProto& request, CreateCallback callback); |
| |
| // Flush flushes a file, like the C standard library's fsync. |
| using FlushCallback = |
| base::OnceCallback<void(const FlushResponseProto& response)>; |
| void Flush(const FlushRequestProto& request, FlushCallback callback); |
| |
| // MkDir is analogous to "/usr/bin/mkdir". |
| using MkDirCallback = |
| base::OnceCallback<void(const MkDirResponseProto& response)>; |
| void MkDir(const MkDirRequestProto& request, MkDirCallback callback); |
| |
| // Open2 opens a virtual file for reading and/or writing. |
| using Open2Callback = |
| base::OnceCallback<void(const Open2ResponseProto& response)>; |
| void Open2(const Open2RequestProto& request, Open2Callback callback); |
| |
| // Rename is analogous to "/usr/bin/mv". |
| using RenameCallback = |
| base::OnceCallback<void(const RenameResponseProto& response)>; |
| void Rename(const RenameRequestProto& request, RenameCallback callback); |
| |
| // Read2 reads from a virtual file opened by Open2. |
| using Read2Callback = |
| base::OnceCallback<void(const Read2ResponseProto& response)>; |
| void Read2(const Read2RequestProto& request, Read2Callback callback); |
| |
| // ReadDir2 lists the directory's children. |
| using ReadDir2Callback = |
| base::OnceCallback<void(const ReadDir2ResponseProto& response)>; |
| void ReadDir2(const ReadDir2RequestProto& request, ReadDir2Callback callback); |
| |
| // RmDir is analogous to "/usr/bin/rmdir". |
| using RmDirCallback = |
| base::OnceCallback<void(const RmDirResponseProto& response)>; |
| void RmDir(const RmDirRequestProto& request, RmDirCallback callback); |
| |
| // Stat2 returns the file or directory's metadata. |
| using Stat2Callback = |
| base::OnceCallback<void(const Stat2ResponseProto& response)>; |
| void Stat2(const Stat2RequestProto& request, Stat2Callback callback); |
| |
| // Truncate sets a file's size. |
| using TruncateCallback = |
| base::OnceCallback<void(const TruncateResponseProto& response)>; |
| void Truncate(const TruncateRequestProto& request, TruncateCallback callback); |
| |
| // Unlink deletes a file. |
| using UnlinkCallback = |
| base::OnceCallback<void(const UnlinkResponseProto& response)>; |
| void Unlink(const UnlinkRequestProto& request, UnlinkCallback callback); |
| |
| // Write2 writes to a virtual file opened by Open2. |
| using Write2Callback = |
| base::OnceCallback<void(const Write2ResponseProto& response)>; |
| void Write2(const Write2RequestProto& request, Write2Callback callback); |
| |
| // File operation D-Bus methods above. Meta D-Bus methods below, which do not |
| // map 1:1 to FUSE or C standard library file operations. |
| |
| // ListStorages returns the active subdir names. Active means passed to |
| // RegisterFSURLPrefix without a subsequent UnregisterFSURLPrefix. |
| using ListStoragesCallback = |
| base::OnceCallback<void(const ListStoragesResponseProto& response)>; |
| void ListStorages(const ListStoragesRequestProto& request, |
| ListStoragesCallback callback); |
| |
| // MakeTempDir makes a temporary directory that has two file paths: an |
| // underlying one (e.g. "/tmp/.foo") and a fusebox one (e.g. |
| // "/media/fuse/fusebox/tmp.foo"). The fusebox one is conceptually similar to |
| // a symbolic link, in that after a "touch /tmp/.foo/bar", bar should be |
| // visible at "/media/fuse/fusebox/tmp.foo/bar", but the 'symbolic link' is |
| // not resolved directly by the kernel. |
| // |
| // Instead, file I/O under the /media/fuse/fusebox mount point goes through |
| // the FuseBox daemon (via FUSE) to Chromium (via D-Bus) to the kernel (as |
| // Chromium storage::FileSystemURL code sees storage::kFileSystemTypeLocal |
| // files living under the underlying file path). |
| // |
| // That sounds convoluted (and overkill for 'sym-linking' a directory on the |
| // local file system), and it is, but it is essentially the same code paths |
| // that FuseBox uses to surface Chromium virtual file systems (VFSs) that are |
| // not otherwise visible on the kernel-level file system. Note that Chromium |
| // VFSs are not the same as Linux kernel VFSs. |
| // |
| // The purpose of these Make/Remove methods is to facilitate testing these |
| // FuseBox code paths (backed by an underlying tmpfs file system) without the |
| // extra complexity of fake VFSs, such as a fake ADP (Android Documents |
| // Provider) or fake MTP (Media Transfer Protocol) back-end. |
| // |
| // MakeTempDir is like "mkdir" (except the callee randomly generates the file |
| // path). RemoveTempDir is like "rm -rf". |
| using MakeTempDirCallback = |
| base::OnceCallback<void(const std::string& error_message, |
| const std::string& fusebox_file_path, |
| const std::string& underlying_file_path)>; |
| void MakeTempDir(MakeTempDirCallback callback); |
| void RemoveTempDir(const std::string& fusebox_file_path); |
| |
| // ---- |
| |
| using PendingFlush = std::pair<FlushRequestProto, FlushCallback>; |
| using PendingRead2 = std::pair<Read2RequestProto, Read2Callback>; |
| using PendingWrite2 = std::pair<Write2RequestProto, Write2Callback>; |
| using PendingOp = absl::variant<PendingFlush, PendingRead2, PendingWrite2>; |
| |
| struct FuseFileMapEntry { |
| FuseFileMapEntry(scoped_refptr<storage::FileSystemContext> fs_context_arg, |
| storage::FileSystemURL fs_url_arg, |
| const std::string& profile_path_arg, |
| bool readable_arg, |
| bool writable_arg, |
| bool use_temp_file_arg, |
| bool temp_file_starts_with_copy_arg); |
| FuseFileMapEntry(FuseFileMapEntry&&); |
| ~FuseFileMapEntry(); |
| |
| void DoFlush(const FlushRequestProto& request, FlushCallback callback); |
| void DoRead2(const Read2RequestProto& request, Read2Callback callback); |
| void DoWrite2(const Write2RequestProto& request, Write2Callback callback); |
| void Do(PendingOp& op, |
| base::WeakPtr<Server> weak_ptr_server, |
| uint64_t fuse_handle); |
| |
| const scoped_refptr<storage::FileSystemContext> fs_context_; |
| const bool readable_; |
| const bool writable_; |
| |
| bool has_in_flight_op_ = false; |
| base::circular_deque<PendingOp> pending_ops_; |
| |
| base::SequenceBound<ReadWriter> seqbnd_read_writer_; |
| }; |
| |
| // Maps from fuse_handle uint64_t values to FileStreamReader / |
| // FileStreamWriter state. |
| using FuseFileMap = std::map<uint64_t, FuseFileMapEntry>; |
| |
| struct PrefixMapEntry { |
| PrefixMapEntry(std::string fs_url_prefix_arg, bool read_only_arg); |
| |
| std::string fs_url_prefix; |
| bool read_only; |
| }; |
| |
| // Maps from a subdir to a storage::FileSystemURL prefix in string form (and |
| // other metadata). For example, the subdir could be the "foo" in the |
| // "/media/fuse/fusebox/foo/bar/baz.txt" filename, which gets mapped to |
| // "fs_url_prefix/bar/baz.txt" before that whole string is parsed as a |
| // storage::FileSystemURL. |
| // |
| // Neither subdir nor fs_url_prefix should have a trailing slash. |
| using PrefixMap = std::map<std::string, PrefixMapEntry>; |
| |
| struct ReadDir2MapEntry { |
| explicit ReadDir2MapEntry(ReadDir2Callback callback); |
| ReadDir2MapEntry(ReadDir2MapEntry&&); |
| ~ReadDir2MapEntry(); |
| |
| // Returns whether the final response was sent. |
| bool Reply(uint64_t cookie, ReadDir2Callback callback); |
| |
| int32_t posix_error_code_ = 0; |
| ReadDir2ResponseProto response_; |
| bool has_more_ = true; |
| |
| ReadDir2Callback callback_; |
| }; |
| |
| // Maps from ReadDir2 cookies to a pair of (1) a buffer of upstream results |
| // from Chromium's storage layer and (2) a possibly-hasnt-arrived-yet pending |
| // downstream ReadDir2Callback (i.e. a D-Bus RPC response). |
| // |
| // If the upstream layer sends its results first then we need to buffer until |
| // we have a downstream callback to pass those results onto. |
| // |
| // If the downstream layer sends its callback first then we need to hold onto |
| // it until we have results to pass on. |
| // |
| // Note that the upstream API works with a base::RepeatingCallback model (one |
| // request, multiple responses) but the downstream API (i.e. D-Bus) works |
| // with a base::OnceCallback model (N requests, N responses). |
| using ReadDir2Map = std::map<uint64_t, ReadDir2MapEntry>; |
| |
| // Maps from a fusebox_file_path (like "/media/fuse/fusebox/tmp.foo") to the |
| // ScopedTempDir that will clean up (in its destructor) the underlying |
| // temporary directory. |
| using TempSubdirMap = std::map<std::string, base::ScopedTempDir>; |
| |
| // ---- |
| |
| private: |
| void ReplyToMakeTempDir(base::ScopedTempDir scoped_temp_dir, |
| bool create_succeeded, |
| MakeTempDirCallback callback); |
| |
| void OnFlush(uint64_t fuse_handle, |
| FlushCallback callback, |
| const FlushResponseProto& response); |
| |
| void OnRead2(uint64_t fuse_handle, |
| Read2Callback callback, |
| const Read2ResponseProto& response); |
| |
| void OnReadDirectory(scoped_refptr<storage::FileSystemContext> fs_context, |
| bool read_only, |
| uint64_t cookie, |
| base::File::Error error_code, |
| storage::AsyncFileUtil::EntryList entry_list, |
| bool has_more); |
| |
| void OnWrite2(uint64_t fuse_handle, |
| Write2Callback callback, |
| const Write2ResponseProto& response); |
| |
| // Removes the entry (if present) for the given map key. |
| void EraseFuseFileMapEntry(uint64_t fuse_handle); |
| // Returns the fuse_handle that is the map key. |
| uint64_t InsertFuseFileMapEntry(FuseFileMapEntry&& entry); |
| |
| raw_ptr<Delegate> delegate_; |
| FuseFileMap fuse_file_map_; |
| fusebox::MonikerMap moniker_map_; |
| PrefixMap prefix_map_; |
| ReadDir2Map read_dir_2_map_; |
| TempSubdirMap temp_subdir_map_; |
| |
| base::WeakPtrFactory<Server> weak_ptr_factory_{this}; |
| }; |
| |
| } // namespace fusebox |
| |
| #endif // CHROME_BROWSER_ASH_FUSEBOX_FUSEBOX_SERVER_H_ |