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_
|