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