File: fusebox_server.h

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (356 lines) | stat: -rw-r--r-- 15,167 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
// 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 <variant>

#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"

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 = std::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_