File: command_storage_backend.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 (301 lines) | stat: -rw-r--r-- 11,826 bytes parent folder | download | duplicates (9)
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
// Copyright 2011 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef COMPONENTS_SESSIONS_CORE_COMMAND_STORAGE_BACKEND_H_
#define COMPONENTS_SESSIONS_CORE_COMMAND_STORAGE_BACKEND_H_

#include <stddef.h>

#include <memory>
#include <optional>
#include <set>
#include <vector>

#include "base/files/file_path.h"
#include "base/functional/callback_forward.h"
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "components/sessions/core/command_storage_manager.h"
#include "components/sessions/core/session_command.h"
#include "components/sessions/core/sessions_export.h"

namespace base {
class Clock;
class File;
}

namespace crypto {
class Aead;
}

namespace sessions {

// CommandStorageBackend is the backend used by CommandStorageManager. It writes
// SessionCommands to disk with the ability to read back at a later date.
// CommandStorageBackend (mostly) does not interpret the commands in any way, it
// simply reads/writes them.
//
// CommandStorageBackend writes to a file with a suffix that indicates the
// time the file was opened. The time stamp allows this code to determine the
// most recently written file. When AppendCommands() is supplied a value of true
// for `truncate`, the current file is closed and a new file is created (with
// a newly generated timestamp). When AppendCommands() successfully writes the
// commands to the file an internal command (whose id is
// `kInitialStateMarkerCommandId`) is written. During startup, the most recent
// file that has the internal command written is used. This ensures restore does
// not attempt to use a file that did not have the complete state written
// (this would happen if chrome crashed while writing the commands, or there
// was a file system error part way through writing).
//
// AppendCommands() takes a callback that is called if there is an error in
// writing to the file. The expectation is if there is an error, the consuming
// code must call AppendCommands() again with `truncate` set to true. If there
// was an error in writing to the file, calls to AppendCommands() with a value
// of false for `truncate` are ignored. This is done to ensure the consuming
// code correctly supplies the initial state.
class SESSIONS_EXPORT CommandStorageBackend
    : public base::RefCountedDeleteOnSequence<CommandStorageBackend> {
 public:
  struct SESSIONS_EXPORT ReadCommandsResult {
    ReadCommandsResult();
    ReadCommandsResult(ReadCommandsResult&& other);
    ReadCommandsResult& operator=(ReadCommandsResult&& other);
    ReadCommandsResult(const ReadCommandsResult&) = delete;
    ReadCommandsResult& operator=(const ReadCommandsResult&) = delete;
    ~ReadCommandsResult();

    std::vector<std::unique_ptr<sessions::SessionCommand>> commands;
    bool error_reading = false;
  };

  using id_type = SessionCommand::id_type;
  using size_type = SessionCommand::size_type;

  // Initial size of the buffer used in reading the file. This is exposed
  // for testing.
  static const int kFileReadBufferSize;

  // Number of bytes encryption adds.
  static const size_type kEncryptionOverheadInBytes;

  // Represents data for a session. Public for tests.
  // Creates a CommandStorageBackend. This method is invoked on the MAIN thread,
  // and does no IO. The real work is done from Init, which is invoked on
  // a background task runer.
  //
  // See `CommandStorageManager` for details on `type` and `path`.
  CommandStorageBackend(
      scoped_refptr<base::SequencedTaskRunner> owning_task_runner,
      const base::FilePath& path,
      CommandStorageManager::SessionType type,
      const std::vector<uint8_t>& decryption_key = {},
      base::Clock* clock = nullptr);
  CommandStorageBackend(const CommandStorageBackend&) = delete;
  CommandStorageBackend& operator=(const CommandStorageBackend&) = delete;

  // Returns true if the file at |path| was generated by this class.
  static bool IsValidFile(const base::FilePath& path);

  // Returns the path the files are being written to.
  const base::FilePath current_path() const {
    return open_file_ ? open_file_->path : base::FilePath();
  }

  bool IsFileOpen() const { return open_file_.get() != nullptr; }

  base::SequencedTaskRunner* owning_task_runner() {
    return base::RefCountedDeleteOnSequence<
        CommandStorageBackend>::owning_task_runner();
  }

  // Appends the specified commands to the current file. If |truncate| is true
  // the file is truncated. If |truncate| is true and |crypto_key| is non-empty,
  // then all commands are encrypted using the supplied key. If there is an
  // error writing the commands, `error_callback` is run.
  void AppendCommands(
      std::vector<std::unique_ptr<sessions::SessionCommand>> commands,
      bool truncate,
      base::OnceClosure error_callback,
      const std::vector<uint8_t>& crypto_key = std::vector<uint8_t>());

  bool inited() const { return inited_; }

  // Parses out the timestamp from a path pointing to a session file.
  static bool TimestampFromPath(const base::FilePath& path, base::Time& result);

  // Returns the set of possible session files. The returned paths are not
  // necessarily valid session files, rather they match the naming criteria
  // for session files.
  static std::set<base::FilePath> GetSessionFilePaths(
      const base::FilePath& path,
      CommandStorageManager::SessionType type);

  // Returns the commands from the last session file.
  ReadCommandsResult ReadLastSessionCommands();

  // Deletes the file containing the commands for the last session.
  void DeleteLastSession();

  // Moves the current session file to the last session file. This is typically
  // called during startup or if the user launches the app and no tabbed
  // browsers are running. After calling this, set_pending_reset() must be
  // called.
  void MoveCurrentSessionToLastSession();

  // Used in testing to emulate an error in writing to the file. The value is
  // automatically reset after the failure.
  void ForceAppendCommandsToFailForTesting();

 private:
  friend class base::RefCountedDeleteOnSequence<CommandStorageBackend>;
  friend class base::DeleteHelper<CommandStorageBackend>;
  friend class CommandStorageBackendTest;

  struct SessionInfo {
    base::FilePath path;
    base::Time timestamp;
  };

  struct OpenFile {
    OpenFile();
    ~OpenFile();

    base::FilePath path;
    std::unique_ptr<base::File> file;
    // Set to true once `kInitialStateMarkerCommandId` is written.
    bool did_write_marker = false;
  };

  ~CommandStorageBackend();

  // Performs initialization on the background task run, calling DoInit() if
  // necessary.
  void InitIfNecessary();

  // Generates the path to a session file with the given timestamp.
  static base::FilePath FilePathFromTime(
      CommandStorageManager::SessionType type,
      const base::FilePath& path,
      base::Time time);

  // Reads the commands from the specified file.  If |crypto_key| is non-empty,
  // it is used to decrypt the file.
  static ReadCommandsResult ReadCommandsFromFile(
      const base::FilePath& path,
      const std::vector<uint8_t>& crypto_key);

  // Closes the file. The next time AppendCommands() is called the file will
  // implicitly be reopened.
  void CloseFile();

  // If current_session_file_ is open, it is truncated so that it is essentially
  // empty (only contains the header). If current_session_file_ isn't open, it
  // is is opened and the header is written to it. After this
  // current_session_file_ contains no commands.
  // NOTE: current_session_file_ may be null if the file couldn't be opened or
  // the header couldn't be written.
  void TruncateOrOpenFile();

  // Opens the current file and writes the header. On success a handle to
  // the file is returned.
  std::unique_ptr<base::File> OpenAndWriteHeader(
      const base::FilePath& path) const;

  // Appends the specified commands to the specified file.
  bool AppendCommandsToFile(
      base::File* file,
      const std::vector<std::unique_ptr<sessions::SessionCommand>>& commands);

  // Writes |command| to |file|. Returns true on success.
  bool AppendCommandToFile(base::File* file,
                           const sessions::SessionCommand& command);

  // Encrypts |command| and writes it to |file|. Returns true on success.
  // The contents of the command and id are encrypted together. This is
  // preceded by the length of the command.
  bool AppendEncryptedCommandToFile(base::File* file,
                                    const sessions::SessionCommand& command);

  // Returns true if commands are encrypted.
  bool IsEncrypted() const { return !crypto_key_.empty(); }

  // Gets data for the last session file.
  std::optional<SessionInfo> FindLastSessionFile() const;

  // Attempt to delete all sessions besides the current and last. This is a
  // best effort operation.
  void DeleteLastSessionFiles() const;

  // Gets all sessions files.
  std::vector<SessionInfo> GetSessionFilesSortedByReverseTimestamp() const {
    return GetSessionFilesSortedByReverseTimestamp(supplied_path_, type_);
  }
  static std::vector<SessionInfo> GetSessionFilesSortedByReverseTimestamp(
      const base::FilePath& path,
      CommandStorageManager::SessionType type);

  static bool CompareSessionInfoTimestamps(const SessionInfo& a,
                                           const SessionInfo& b) {
    return b.timestamp < a.timestamp;
  }

  // Returns true if `path` can be used for the last session.
  bool CanUseFileForLastSession(const base::FilePath& path) const;

  const CommandStorageManager::SessionType type_;

  // This is the path supplied to the constructor. See CommandStorageManager
  // constructor for details.
  const base::FilePath supplied_path_;

  // Used to decode the initial last session file.
  // TODO(sky): this is currently required because InitIfNecessary() determines
  // the last file. If that can be delayed, then this can be supplied to
  // GetLastSessionCommands().
  const std::vector<uint8_t> initial_decryption_key_;

  // TaskRunner that the callback is added to.
  scoped_refptr<base::SequencedTaskRunner> callback_task_runner_;

  raw_ptr<base::Clock> clock_;

  // File and path commands are being written.
  std::unique_ptr<OpenFile> open_file_;

  // Whether DoInit() was called. DoInit() is called on the background task
  // runner.
  bool inited_ = false;

  std::vector<uint8_t> crypto_key_;
  std::unique_ptr<crypto::Aead> aead_;

  // Incremented every time a command is written.
  int commands_written_ = 0;

  // Timestamp when this session was started.
  base::Time timestamp_;

  // Data for the last session. If unset, fallback to legacy session data.
  std::optional<SessionInfo> last_session_info_;

  // Paths of the two most recently written files with a valid marker (the
  // first of which may be the currently open file). When a new file is
  // successfully opened and the initial set of commands is written,
  // `last_or_current_path_with_valid_marker_` is set to the path. At this
  // point the previous file (initial value of
  // `last_or_current_path_with_valid_marker_`) is no longer needed, and can be
  // deleted. As there is no guarantee the commands have actually been written
  // to disk, we keep one additional file around.
  // `second_to_last_path_with_valid_marker_` maintains the previous valid file
  // with a marker.
  std::optional<base::FilePath> last_or_current_path_with_valid_marker_;
  std::optional<base::FilePath> second_to_last_path_with_valid_marker_;

  bool force_append_commands_to_fail_for_testing_ = false;
};

}  // namespace sessions

#endif  // COMPONENTS_SESSIONS_CORE_COMMAND_STORAGE_BACKEND_H_