[go: nahoru, domu]

blob: d77e3ec54caab55457a236f875a9a3db4474aa06 [file] [log] [blame]
// 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_