Port Android relocation packer to chromium build
[chromium-blink-merge.git] / components / sessions / session_backend.cc
blob99520b42505b527989308d3ba6b2c772b649530f
1 // Copyright 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/sessions/session_backend.h"
7 #include <limits>
9 #include "base/files/file.h"
10 #include "base/files/file_util.h"
11 #include "base/memory/scoped_vector.h"
12 #include "base/metrics/histogram.h"
13 #include "base/threading/thread_restrictions.h"
15 using base::TimeTicks;
17 namespace sessions {
19 // File version number.
20 static const int32 kFileCurrentVersion = 1;
22 // The signature at the beginning of the file = SSNS (Sessions).
23 static const int32 kFileSignature = 0x53534E53;
25 namespace {
27 // The file header is the first bytes written to the file,
28 // and is used to identify the file as one written by us.
29 struct FileHeader {
30 int32 signature;
31 int32 version;
34 // SessionFileReader ----------------------------------------------------------
36 // SessionFileReader is responsible for reading the set of SessionCommands that
37 // describe a Session back from a file. SessionFileRead does minimal error
38 // checking on the file (pretty much only that the header is valid).
40 class SessionFileReader {
41 public:
42 typedef sessions::SessionCommand::id_type id_type;
43 typedef sessions::SessionCommand::size_type size_type;
45 explicit SessionFileReader(const base::FilePath& path)
46 : errored_(false),
47 buffer_(SessionBackend::kFileReadBufferSize, 0),
48 buffer_position_(0),
49 available_count_(0) {
50 file_.reset(new base::File(
51 path, base::File::FLAG_OPEN | base::File::FLAG_READ));
53 // Reads the contents of the file specified in the constructor, returning
54 // true on success. It is up to the caller to free all SessionCommands
55 // added to commands.
56 bool Read(sessions::BaseSessionService::SessionType type,
57 ScopedVector<sessions::SessionCommand>* commands);
59 private:
60 // Reads a single command, returning it. A return value of NULL indicates
61 // either there are no commands, or there was an error. Use errored_ to
62 // distinguish the two. If NULL is returned, and there is no error, it means
63 // the end of file was successfully reached.
64 sessions::SessionCommand* ReadCommand();
66 // Shifts the unused portion of buffer_ to the beginning and fills the
67 // remaining portion with data from the file. Returns false if the buffer
68 // couldn't be filled. A return value of false only signals an error if
69 // errored_ is set to true.
70 bool FillBuffer();
72 // Whether an error condition has been detected (
73 bool errored_;
75 // As we read from the file, data goes here.
76 std::string buffer_;
78 // The file.
79 scoped_ptr<base::File> file_;
81 // Position in buffer_ of the data.
82 size_t buffer_position_;
84 // Number of available bytes; relative to buffer_position_.
85 size_t available_count_;
87 DISALLOW_COPY_AND_ASSIGN(SessionFileReader);
90 bool SessionFileReader::Read(sessions::BaseSessionService::SessionType type,
91 ScopedVector<sessions::SessionCommand>* commands) {
92 if (!file_->IsValid())
93 return false;
94 FileHeader header;
95 int read_count;
96 TimeTicks start_time = TimeTicks::Now();
97 read_count = file_->ReadAtCurrentPos(reinterpret_cast<char*>(&header),
98 sizeof(header));
99 if (read_count != sizeof(header) || header.signature != kFileSignature ||
100 header.version != kFileCurrentVersion)
101 return false;
103 ScopedVector<sessions::SessionCommand> read_commands;
104 for (sessions::SessionCommand* command = ReadCommand(); command && !errored_;
105 command = ReadCommand())
106 read_commands.push_back(command);
107 if (!errored_)
108 read_commands.swap(*commands);
109 if (type == sessions::BaseSessionService::TAB_RESTORE) {
110 UMA_HISTOGRAM_TIMES("TabRestore.read_session_file_time",
111 TimeTicks::Now() - start_time);
112 } else {
113 UMA_HISTOGRAM_TIMES("SessionRestore.read_session_file_time",
114 TimeTicks::Now() - start_time);
116 return !errored_;
119 sessions::SessionCommand* SessionFileReader::ReadCommand() {
120 // Make sure there is enough in the buffer for the size of the next command.
121 if (available_count_ < sizeof(size_type)) {
122 if (!FillBuffer())
123 return NULL;
124 if (available_count_ < sizeof(size_type)) {
125 VLOG(1) << "SessionFileReader::ReadCommand, file incomplete";
126 // Still couldn't read a valid size for the command, assume write was
127 // incomplete and return NULL.
128 return NULL;
131 // Get the size of the command.
132 size_type command_size;
133 memcpy(&command_size, &(buffer_[buffer_position_]), sizeof(command_size));
134 buffer_position_ += sizeof(command_size);
135 available_count_ -= sizeof(command_size);
137 if (command_size == 0) {
138 VLOG(1) << "SessionFileReader::ReadCommand, empty command";
139 // Empty command. Shouldn't happen if write was successful, fail.
140 return NULL;
143 // Make sure buffer has the complete contents of the command.
144 if (command_size > available_count_) {
145 if (command_size > buffer_.size())
146 buffer_.resize((command_size / 1024 + 1) * 1024, 0);
147 if (!FillBuffer() || command_size > available_count_) {
148 // Again, assume the file was ok, and just the last chunk was lost.
149 VLOG(1) << "SessionFileReader::ReadCommand, last chunk lost";
150 return NULL;
153 const id_type command_id = buffer_[buffer_position_];
154 // NOTE: command_size includes the size of the id, which is not part of
155 // the contents of the SessionCommand.
156 sessions::SessionCommand* command =
157 new sessions::SessionCommand(command_id, command_size - sizeof(id_type));
158 if (command_size > sizeof(id_type)) {
159 memcpy(command->contents(),
160 &(buffer_[buffer_position_ + sizeof(id_type)]),
161 command_size - sizeof(id_type));
163 buffer_position_ += command_size;
164 available_count_ -= command_size;
165 return command;
168 bool SessionFileReader::FillBuffer() {
169 if (available_count_ > 0 && buffer_position_ > 0) {
170 // Shift buffer to beginning.
171 memmove(&(buffer_[0]), &(buffer_[buffer_position_]), available_count_);
173 buffer_position_ = 0;
174 DCHECK(buffer_position_ + available_count_ < buffer_.size());
175 int to_read = static_cast<int>(buffer_.size() - available_count_);
176 int read_count = file_->ReadAtCurrentPos(&(buffer_[available_count_]),
177 to_read);
178 if (read_count < 0) {
179 errored_ = true;
180 return false;
182 if (read_count == 0)
183 return false;
184 available_count_ += read_count;
185 return true;
188 } // namespace
190 // SessionBackend -------------------------------------------------------------
192 // File names (current and previous) for a type of TAB.
193 static const char* kCurrentTabSessionFileName = "Current Tabs";
194 static const char* kLastTabSessionFileName = "Last Tabs";
196 // File names (current and previous) for a type of SESSION.
197 static const char* kCurrentSessionFileName = "Current Session";
198 static const char* kLastSessionFileName = "Last Session";
200 // static
201 const int SessionBackend::kFileReadBufferSize = 1024;
203 SessionBackend::SessionBackend(sessions::BaseSessionService::SessionType type,
204 const base::FilePath& path_to_dir)
205 : type_(type),
206 path_to_dir_(path_to_dir),
207 last_session_valid_(false),
208 inited_(false),
209 empty_file_(true) {
210 // NOTE: this is invoked on the main thread, don't do file access here.
213 void SessionBackend::Init() {
214 if (inited_)
215 return;
217 inited_ = true;
219 // Create the directory for session info.
220 base::CreateDirectory(path_to_dir_);
222 MoveCurrentSessionToLastSession();
225 void SessionBackend::AppendCommands(
226 ScopedVector<sessions::SessionCommand> commands,
227 bool reset_first) {
228 Init();
229 // Make sure and check current_session_file_, if opening the file failed
230 // current_session_file_ will be NULL.
231 if ((reset_first && !empty_file_) || !current_session_file_.get() ||
232 !current_session_file_->IsValid()) {
233 ResetFile();
235 // Need to check current_session_file_ again, ResetFile may fail.
236 if (current_session_file_.get() && current_session_file_->IsValid() &&
237 !AppendCommandsToFile(current_session_file_.get(), commands)) {
238 current_session_file_.reset(NULL);
240 empty_file_ = false;
243 void SessionBackend::ReadLastSessionCommands(
244 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled,
245 const sessions::BaseSessionService::GetCommandsCallback& callback) {
246 if (is_canceled.Run())
247 return;
249 Init();
251 ScopedVector<sessions::SessionCommand> commands;
252 ReadLastSessionCommandsImpl(&commands);
253 callback.Run(commands.Pass());
256 bool SessionBackend::ReadLastSessionCommandsImpl(
257 ScopedVector<sessions::SessionCommand>* commands) {
258 Init();
259 SessionFileReader file_reader(GetLastSessionPath());
260 return file_reader.Read(type_, commands);
263 void SessionBackend::DeleteLastSession() {
264 Init();
265 base::DeleteFile(GetLastSessionPath(), false);
268 void SessionBackend::MoveCurrentSessionToLastSession() {
269 Init();
270 current_session_file_.reset(NULL);
272 const base::FilePath current_session_path = GetCurrentSessionPath();
273 const base::FilePath last_session_path = GetLastSessionPath();
274 if (base::PathExists(last_session_path))
275 base::DeleteFile(last_session_path, false);
276 if (base::PathExists(current_session_path)) {
277 int64 file_size;
278 if (base::GetFileSize(current_session_path, &file_size)) {
279 if (type_ == sessions::BaseSessionService::TAB_RESTORE) {
280 UMA_HISTOGRAM_COUNTS("TabRestore.last_session_file_size",
281 static_cast<int>(file_size / 1024));
282 } else {
283 UMA_HISTOGRAM_COUNTS("SessionRestore.last_session_file_size",
284 static_cast<int>(file_size / 1024));
287 last_session_valid_ = base::Move(current_session_path, last_session_path);
290 if (base::PathExists(current_session_path))
291 base::DeleteFile(current_session_path, false);
293 // Create and open the file for the current session.
294 ResetFile();
297 bool SessionBackend::ReadCurrentSessionCommandsImpl(
298 ScopedVector<sessions::SessionCommand>* commands) {
299 Init();
300 SessionFileReader file_reader(GetCurrentSessionPath());
301 return file_reader.Read(type_, commands);
304 bool SessionBackend::AppendCommandsToFile(base::File* file,
305 const ScopedVector<sessions::SessionCommand>& commands) {
306 for (ScopedVector<sessions::SessionCommand>::const_iterator i =
307 commands.begin();
308 i != commands.end(); ++i) {
309 int wrote;
310 const size_type content_size = static_cast<size_type>((*i)->size());
311 const size_type total_size = content_size + sizeof(id_type);
312 if (type_ == sessions::BaseSessionService::TAB_RESTORE)
313 UMA_HISTOGRAM_COUNTS("TabRestore.command_size", total_size);
314 else
315 UMA_HISTOGRAM_COUNTS("SessionRestore.command_size", total_size);
316 wrote = file->WriteAtCurrentPos(reinterpret_cast<const char*>(&total_size),
317 sizeof(total_size));
318 if (wrote != sizeof(total_size)) {
319 NOTREACHED() << "error writing";
320 return false;
322 id_type command_id = (*i)->id();
323 wrote = file->WriteAtCurrentPos(reinterpret_cast<char*>(&command_id),
324 sizeof(command_id));
325 if (wrote != sizeof(command_id)) {
326 NOTREACHED() << "error writing";
327 return false;
329 if (content_size > 0) {
330 wrote = file->WriteAtCurrentPos(reinterpret_cast<char*>((*i)->contents()),
331 content_size);
332 if (wrote != content_size) {
333 NOTREACHED() << "error writing";
334 return false;
338 #if defined(OS_CHROMEOS)
339 file->Flush();
340 #endif
341 return true;
344 SessionBackend::~SessionBackend() {
345 if (current_session_file_.get()) {
346 // Destructor performs file IO because file is open in sync mode.
347 // crbug.com/112512.
348 base::ThreadRestrictions::ScopedAllowIO allow_io;
349 current_session_file_.reset();
353 void SessionBackend::ResetFile() {
354 DCHECK(inited_);
355 if (current_session_file_.get()) {
356 // File is already open, truncate it. We truncate instead of closing and
357 // reopening to avoid the possibility of scanners locking the file out
358 // from under us once we close it. If truncation fails, we'll try to
359 // recreate.
360 const int header_size = static_cast<int>(sizeof(FileHeader));
361 if (current_session_file_->Seek(
362 base::File::FROM_BEGIN, header_size) != header_size ||
363 !current_session_file_->SetLength(header_size))
364 current_session_file_.reset(NULL);
366 if (!current_session_file_.get())
367 current_session_file_.reset(OpenAndWriteHeader(GetCurrentSessionPath()));
368 empty_file_ = true;
371 base::File* SessionBackend::OpenAndWriteHeader(const base::FilePath& path) {
372 DCHECK(!path.empty());
373 scoped_ptr<base::File> file(new base::File(
374 path,
375 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE |
376 base::File::FLAG_EXCLUSIVE_WRITE | base::File::FLAG_EXCLUSIVE_READ));
377 if (!file->IsValid())
378 return NULL;
379 FileHeader header;
380 header.signature = kFileSignature;
381 header.version = kFileCurrentVersion;
382 int wrote = file->WriteAtCurrentPos(reinterpret_cast<char*>(&header),
383 sizeof(header));
384 if (wrote != sizeof(header))
385 return NULL;
386 return file.release();
389 base::FilePath SessionBackend::GetLastSessionPath() {
390 base::FilePath path = path_to_dir_;
391 if (type_ == sessions::BaseSessionService::TAB_RESTORE)
392 path = path.AppendASCII(kLastTabSessionFileName);
393 else
394 path = path.AppendASCII(kLastSessionFileName);
395 return path;
398 base::FilePath SessionBackend::GetCurrentSessionPath() {
399 base::FilePath path = path_to_dir_;
400 if (type_ == sessions::BaseSessionService::TAB_RESTORE)
401 path = path.AppendASCII(kCurrentTabSessionFileName);
402 else
403 path = path.AppendASCII(kCurrentSessionFileName);
404 return path;
407 } // namespace sessions