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