1 // Copyright (c) 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 "chrome/browser/sessions/session_backend.h" 6 7 #include <limits> 8 9 #include "base/file_util.h" 10 #include "base/files/file.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 // File version number. 18 static const int32 kFileCurrentVersion = 1; 19 20 // The signature at the beginning of the file = SSNS (Sessions). 21 static const int32 kFileSignature = 0x53534E53; 22 23 namespace { 24 25 // The file header is the first bytes written to the file, 26 // and is used to identify the file as one written by us. 27 struct FileHeader { 28 int32 signature; 29 int32 version; 30 }; 31 32 // SessionFileReader ---------------------------------------------------------- 33 34 // SessionFileReader is responsible for reading the set of SessionCommands that 35 // describe a Session back from a file. SessionFileRead does minimal error 36 // checking on the file (pretty much only that the header is valid). 37 38 class SessionFileReader { 39 public: 40 typedef SessionCommand::id_type id_type; 41 typedef SessionCommand::size_type size_type; 42 43 explicit SessionFileReader(const base::FilePath& path) 44 : errored_(false), 45 buffer_(SessionBackend::kFileReadBufferSize, 0), 46 buffer_position_(0), 47 available_count_(0) { 48 file_.reset(new base::File( 49 path, base::File::FLAG_OPEN | base::File::FLAG_READ)); 50 } 51 // Reads the contents of the file specified in the constructor, returning 52 // true on success. It is up to the caller to free all SessionCommands 53 // added to commands. 54 bool Read(BaseSessionService::SessionType type, 55 std::vector<SessionCommand*>* commands); 56 57 private: 58 // Reads a single command, returning it. A return value of NULL indicates 59 // either there are no commands, or there was an error. Use errored_ to 60 // distinguish the two. If NULL is returned, and there is no error, it means 61 // the end of file was successfully reached. 62 SessionCommand* ReadCommand(); 63 64 // Shifts the unused portion of buffer_ to the beginning and fills the 65 // remaining portion with data from the file. Returns false if the buffer 66 // couldn't be filled. A return value of false only signals an error if 67 // errored_ is set to true. 68 bool FillBuffer(); 69 70 // Whether an error condition has been detected ( 71 bool errored_; 72 73 // As we read from the file, data goes here. 74 std::string buffer_; 75 76 // The file. 77 scoped_ptr<base::File> file_; 78 79 // Position in buffer_ of the data. 80 size_t buffer_position_; 81 82 // Number of available bytes; relative to buffer_position_. 83 size_t available_count_; 84 85 DISALLOW_COPY_AND_ASSIGN(SessionFileReader); 86 }; 87 88 bool SessionFileReader::Read(BaseSessionService::SessionType type, 89 std::vector<SessionCommand*>* commands) { 90 if (!file_->IsValid()) 91 return false; 92 FileHeader header; 93 int read_count; 94 TimeTicks start_time = TimeTicks::Now(); 95 read_count = file_->ReadAtCurrentPos(reinterpret_cast<char*>(&header), 96 sizeof(header)); 97 if (read_count != sizeof(header) || header.signature != kFileSignature || 98 header.version != kFileCurrentVersion) 99 return false; 100 101 ScopedVector<SessionCommand> read_commands; 102 SessionCommand* command; 103 while ((command = ReadCommand()) && !errored_) 104 read_commands.push_back(command); 105 if (!errored_) 106 read_commands.swap(*commands); 107 if (type == BaseSessionService::TAB_RESTORE) { 108 UMA_HISTOGRAM_TIMES("TabRestore.read_session_file_time", 109 TimeTicks::Now() - start_time); 110 } else { 111 UMA_HISTOGRAM_TIMES("SessionRestore.read_session_file_time", 112 TimeTicks::Now() - start_time); 113 } 114 return !errored_; 115 } 116 117 SessionCommand* SessionFileReader::ReadCommand() { 118 // Make sure there is enough in the buffer for the size of the next command. 119 if (available_count_ < sizeof(size_type)) { 120 if (!FillBuffer()) 121 return NULL; 122 if (available_count_ < sizeof(size_type)) { 123 VLOG(1) << "SessionFileReader::ReadCommand, file incomplete"; 124 // Still couldn't read a valid size for the command, assume write was 125 // incomplete and return NULL. 126 return NULL; 127 } 128 } 129 // Get the size of the command. 130 size_type command_size; 131 memcpy(&command_size, &(buffer_[buffer_position_]), sizeof(command_size)); 132 buffer_position_ += sizeof(command_size); 133 available_count_ -= sizeof(command_size); 134 135 if (command_size == 0) { 136 VLOG(1) << "SessionFileReader::ReadCommand, empty command"; 137 // Empty command. Shouldn't happen if write was successful, fail. 138 return NULL; 139 } 140 141 // Make sure buffer has the complete contents of the command. 142 if (command_size > available_count_) { 143 if (command_size > buffer_.size()) 144 buffer_.resize((command_size / 1024 + 1) * 1024, 0); 145 if (!FillBuffer() || command_size > available_count_) { 146 // Again, assume the file was ok, and just the last chunk was lost. 147 VLOG(1) << "SessionFileReader::ReadCommand, last chunk lost"; 148 return NULL; 149 } 150 } 151 const id_type command_id = buffer_[buffer_position_]; 152 // NOTE: command_size includes the size of the id, which is not part of 153 // the contents of the SessionCommand. 154 SessionCommand* command = 155 new SessionCommand(command_id, command_size - sizeof(id_type)); 156 if (command_size > sizeof(id_type)) { 157 memcpy(command->contents(), 158 &(buffer_[buffer_position_ + sizeof(id_type)]), 159 command_size - sizeof(id_type)); 160 } 161 buffer_position_ += command_size; 162 available_count_ -= command_size; 163 return command; 164 } 165 166 bool SessionFileReader::FillBuffer() { 167 if (available_count_ > 0 && buffer_position_ > 0) { 168 // Shift buffer to beginning. 169 memmove(&(buffer_[0]), &(buffer_[buffer_position_]), available_count_); 170 } 171 buffer_position_ = 0; 172 DCHECK(buffer_position_ + available_count_ < buffer_.size()); 173 int to_read = static_cast<int>(buffer_.size() - available_count_); 174 int read_count = file_->ReadAtCurrentPos(&(buffer_[available_count_]), 175 to_read); 176 if (read_count < 0) { 177 errored_ = true; 178 return false; 179 } 180 if (read_count == 0) 181 return false; 182 available_count_ += read_count; 183 return true; 184 } 185 186 } // namespace 187 188 // SessionBackend ------------------------------------------------------------- 189 190 // File names (current and previous) for a type of TAB. 191 static const char* kCurrentTabSessionFileName = "Current Tabs"; 192 static const char* kLastTabSessionFileName = "Last Tabs"; 193 194 // File names (current and previous) for a type of SESSION. 195 static const char* kCurrentSessionFileName = "Current Session"; 196 static const char* kLastSessionFileName = "Last Session"; 197 198 // static 199 const int SessionBackend::kFileReadBufferSize = 1024; 200 201 SessionBackend::SessionBackend(BaseSessionService::SessionType type, 202 const base::FilePath& path_to_dir) 203 : type_(type), 204 path_to_dir_(path_to_dir), 205 last_session_valid_(false), 206 inited_(false), 207 empty_file_(true) { 208 // NOTE: this is invoked on the main thread, don't do file access here. 209 } 210 211 void SessionBackend::Init() { 212 if (inited_) 213 return; 214 215 inited_ = true; 216 217 // Create the directory for session info. 218 base::CreateDirectory(path_to_dir_); 219 220 MoveCurrentSessionToLastSession(); 221 } 222 223 void SessionBackend::AppendCommands( 224 std::vector<SessionCommand*>* commands, 225 bool reset_first) { 226 Init(); 227 // Make sure and check current_session_file_, if opening the file failed 228 // current_session_file_ will be NULL. 229 if ((reset_first && !empty_file_) || !current_session_file_.get() || 230 !current_session_file_->IsValid()) { 231 ResetFile(); 232 } 233 // Need to check current_session_file_ again, ResetFile may fail. 234 if (current_session_file_.get() && current_session_file_->IsValid() && 235 !AppendCommandsToFile(current_session_file_.get(), *commands)) { 236 current_session_file_.reset(NULL); 237 } 238 empty_file_ = false; 239 STLDeleteElements(commands); 240 delete commands; 241 } 242 243 void SessionBackend::ReadLastSessionCommands( 244 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, 245 const BaseSessionService::InternalGetCommandsCallback& callback) { 246 if (is_canceled.Run()) 247 return; 248 249 Init(); 250 251 ScopedVector<SessionCommand> commands; 252 ReadLastSessionCommandsImpl(&(commands.get())); 253 callback.Run(commands.Pass()); 254 } 255 256 bool SessionBackend::ReadLastSessionCommandsImpl( 257 std::vector<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_ == 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 std::vector<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 std::vector<SessionCommand*>& commands) { 306 for (std::vector<SessionCommand*>::const_iterator i = commands.begin(); 307 i != commands.end(); ++i) { 308 int wrote; 309 const size_type content_size = static_cast<size_type>((*i)->size()); 310 const size_type total_size = content_size + sizeof(id_type); 311 if (type_ == BaseSessionService::TAB_RESTORE) 312 UMA_HISTOGRAM_COUNTS("TabRestore.command_size", total_size); 313 else 314 UMA_HISTOGRAM_COUNTS("SessionRestore.command_size", total_size); 315 wrote = file->WriteAtCurrentPos(reinterpret_cast<const char*>(&total_size), 316 sizeof(total_size)); 317 if (wrote != sizeof(total_size)) { 318 NOTREACHED() << "error writing"; 319 return false; 320 } 321 id_type command_id = (*i)->id(); 322 wrote = file->WriteAtCurrentPos(reinterpret_cast<char*>(&command_id), 323 sizeof(command_id)); 324 if (wrote != sizeof(command_id)) { 325 NOTREACHED() << "error writing"; 326 return false; 327 } 328 if (content_size > 0) { 329 wrote = file->WriteAtCurrentPos(reinterpret_cast<char*>((*i)->contents()), 330 content_size); 331 if (wrote != content_size) { 332 NOTREACHED() << "error writing"; 333 return false; 334 } 335 } 336 } 337 #if defined(OS_CHROMEOS) 338 file->Flush(); 339 #endif 340 return true; 341 } 342 343 SessionBackend::~SessionBackend() { 344 if (current_session_file_.get()) { 345 // Destructor performs file IO because file is open in sync mode. 346 // crbug.com/112512. 347 base::ThreadRestrictions::ScopedAllowIO allow_io; 348 current_session_file_.reset(); 349 } 350 } 351 352 void SessionBackend::ResetFile() { 353 DCHECK(inited_); 354 if (current_session_file_.get()) { 355 // File is already open, truncate it. We truncate instead of closing and 356 // reopening to avoid the possibility of scanners locking the file out 357 // from under us once we close it. If truncation fails, we'll try to 358 // recreate. 359 const int header_size = static_cast<int>(sizeof(FileHeader)); 360 if (current_session_file_->Seek( 361 base::File::FROM_BEGIN, header_size) != header_size || 362 !current_session_file_->SetLength(header_size)) 363 current_session_file_.reset(NULL); 364 } 365 if (!current_session_file_.get()) 366 current_session_file_.reset(OpenAndWriteHeader(GetCurrentSessionPath())); 367 empty_file_ = true; 368 } 369 370 base::File* SessionBackend::OpenAndWriteHeader(const base::FilePath& path) { 371 DCHECK(!path.empty()); 372 scoped_ptr<base::File> file(new base::File( 373 path, 374 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | 375 base::File::FLAG_EXCLUSIVE_WRITE | base::File::FLAG_EXCLUSIVE_READ)); 376 if (!file->IsValid()) 377 return NULL; 378 FileHeader header; 379 header.signature = kFileSignature; 380 header.version = kFileCurrentVersion; 381 int wrote = file->WriteAtCurrentPos(reinterpret_cast<char*>(&header), 382 sizeof(header)); 383 if (wrote != sizeof(header)) 384 return NULL; 385 return file.release(); 386 } 387 388 base::FilePath SessionBackend::GetLastSessionPath() { 389 base::FilePath path = path_to_dir_; 390 if (type_ == BaseSessionService::TAB_RESTORE) 391 path = path.AppendASCII(kLastTabSessionFileName); 392 else 393 path = path.AppendASCII(kLastSessionFileName); 394 return path; 395 } 396 397 base::FilePath SessionBackend::GetCurrentSessionPath() { 398 base::FilePath path = path_to_dir_; 399 if (type_ == BaseSessionService::TAB_RESTORE) 400 path = path.AppendASCII(kCurrentTabSessionFileName); 401 else 402 path = path.AppendASCII(kCurrentSessionFileName); 403 return path; 404 } 405