| OLD | NEW |
| (Empty) |
| 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. | |
| 4 | |
| 5 #include "components/sessions/session_backend.h" | |
| 6 | |
| 7 #include <limits> | |
| 8 | |
| 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" | |
| 14 | |
| 15 using base::TimeTicks; | |
| 16 | |
| 17 namespace sessions { | |
| 18 | |
| 19 // File version number. | |
| 20 static const int32 kFileCurrentVersion = 1; | |
| 21 | |
| 22 // The signature at the beginning of the file = SSNS (Sessions). | |
| 23 static const int32 kFileSignature = 0x53534E53; | |
| 24 | |
| 25 namespace { | |
| 26 | |
| 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; | |
| 32 }; | |
| 33 | |
| 34 // SessionFileReader ---------------------------------------------------------- | |
| 35 | |
| 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). | |
| 39 | |
| 40 class SessionFileReader { | |
| 41 public: | |
| 42 typedef sessions::SessionCommand::id_type id_type; | |
| 43 typedef sessions::SessionCommand::size_type size_type; | |
| 44 | |
| 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)); | |
| 52 } | |
| 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); | |
| 58 | |
| 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(); | |
| 65 | |
| 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(); | |
| 71 | |
| 72 // Whether an error condition has been detected ( | |
| 73 bool errored_; | |
| 74 | |
| 75 // As we read from the file, data goes here. | |
| 76 std::string buffer_; | |
| 77 | |
| 78 // The file. | |
| 79 scoped_ptr<base::File> file_; | |
| 80 | |
| 81 // Position in buffer_ of the data. | |
| 82 size_t buffer_position_; | |
| 83 | |
| 84 // Number of available bytes; relative to buffer_position_. | |
| 85 size_t available_count_; | |
| 86 | |
| 87 DISALLOW_COPY_AND_ASSIGN(SessionFileReader); | |
| 88 }; | |
| 89 | |
| 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; | |
| 102 | |
| 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); | |
| 115 } | |
| 116 return !errored_; | |
| 117 } | |
| 118 | |
| 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; | |
| 129 } | |
| 130 } | |
| 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); | |
| 136 | |
| 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; | |
| 141 } | |
| 142 | |
| 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; | |
| 151 } | |
| 152 } | |
| 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)); | |
| 162 } | |
| 163 buffer_position_ += command_size; | |
| 164 available_count_ -= command_size; | |
| 165 return command; | |
| 166 } | |
| 167 | |
| 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_); | |
| 172 } | |
| 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; | |
| 181 } | |
| 182 if (read_count == 0) | |
| 183 return false; | |
| 184 available_count_ += read_count; | |
| 185 return true; | |
| 186 } | |
| 187 | |
| 188 } // namespace | |
| 189 | |
| 190 // SessionBackend ------------------------------------------------------------- | |
| 191 | |
| 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"; | |
| 195 | |
| 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"; | |
| 199 | |
| 200 // static | |
| 201 const int SessionBackend::kFileReadBufferSize = 1024; | |
| 202 | |
| 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. | |
| 211 } | |
| 212 | |
| 213 void SessionBackend::Init() { | |
| 214 if (inited_) | |
| 215 return; | |
| 216 | |
| 217 inited_ = true; | |
| 218 | |
| 219 // Create the directory for session info. | |
| 220 base::CreateDirectory(path_to_dir_); | |
| 221 | |
| 222 MoveCurrentSessionToLastSession(); | |
| 223 } | |
| 224 | |
| 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(); | |
| 234 } | |
| 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); | |
| 239 } | |
| 240 empty_file_ = false; | |
| 241 } | |
| 242 | |
| 243 void SessionBackend::ReadLastSessionCommands( | |
| 244 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, | |
| 245 const sessions::BaseSessionService::GetCommandsCallback& callback) { | |
| 246 if (is_canceled.Run()) | |
| 247 return; | |
| 248 | |
| 249 Init(); | |
| 250 | |
| 251 ScopedVector<sessions::SessionCommand> commands; | |
| 252 ReadLastSessionCommandsImpl(&commands); | |
| 253 callback.Run(commands.Pass()); | |
| 254 } | |
| 255 | |
| 256 bool SessionBackend::ReadLastSessionCommandsImpl( | |
| 257 ScopedVector<sessions::SessionCommand>* commands) { | |
| 258 Init(); | |
| 259 SessionFileReader file_reader(GetLastSessionPath()); | |
| 260 return file_reader.Read(type_, commands); | |
| 261 } | |
| 262 | |
| 263 void SessionBackend::DeleteLastSession() { | |
| 264 Init(); | |
| 265 base::DeleteFile(GetLastSessionPath(), false); | |
| 266 } | |
| 267 | |
| 268 void SessionBackend::MoveCurrentSessionToLastSession() { | |
| 269 Init(); | |
| 270 current_session_file_.reset(NULL); | |
| 271 | |
| 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)); | |
| 285 } | |
| 286 } | |
| 287 last_session_valid_ = base::Move(current_session_path, last_session_path); | |
| 288 } | |
| 289 | |
| 290 if (base::PathExists(current_session_path)) | |
| 291 base::DeleteFile(current_session_path, false); | |
| 292 | |
| 293 // Create and open the file for the current session. | |
| 294 ResetFile(); | |
| 295 } | |
| 296 | |
| 297 bool SessionBackend::ReadCurrentSessionCommandsImpl( | |
| 298 ScopedVector<sessions::SessionCommand>* commands) { | |
| 299 Init(); | |
| 300 SessionFileReader file_reader(GetCurrentSessionPath()); | |
| 301 return file_reader.Read(type_, commands); | |
| 302 } | |
| 303 | |
| 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; | |
| 321 } | |
| 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; | |
| 328 } | |
| 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; | |
| 335 } | |
| 336 } | |
| 337 } | |
| 338 #if defined(OS_CHROMEOS) | |
| 339 file->Flush(); | |
| 340 #endif | |
| 341 return true; | |
| 342 } | |
| 343 | |
| 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(); | |
| 350 } | |
| 351 } | |
| 352 | |
| 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); | |
| 365 } | |
| 366 if (!current_session_file_.get()) | |
| 367 current_session_file_.reset(OpenAndWriteHeader(GetCurrentSessionPath())); | |
| 368 empty_file_ = true; | |
| 369 } | |
| 370 | |
| 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(); | |
| 387 } | |
| 388 | |
| 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; | |
| 396 } | |
| 397 | |
| 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; | |
| 405 } | |
| 406 | |
| 407 } // namespace sessions | |
| OLD | NEW |