1 // Copyright (c) 2011 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 "net/base/file_stream.h" 13 #include "net/base/net_errors.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 FilePath& path) 44 : errored_(false), 45 buffer_(SessionBackend::kFileReadBufferSize, 0), 46 buffer_position_(0), 47 available_count_(0) { 48 file_.reset(new net::FileStream()); 49 file_->Open(path, base::PLATFORM_FILE_OPEN | base::PLATFORM_FILE_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<net::FileStream> 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_->IsOpen()) 91 return false; 92 FileHeader header; 93 int read_count; 94 TimeTicks start_time = TimeTicks::Now(); 95 read_count = file_->ReadUntilComplete(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 // Still couldn't read a valid size for the command, assume write was 124 // incomplete and return NULL. 125 return NULL; 126 } 127 } 128 // Get the size of the command. 129 size_type command_size; 130 memcpy(&command_size, &(buffer_[buffer_position_]), sizeof(command_size)); 131 buffer_position_ += sizeof(command_size); 132 available_count_ -= sizeof(command_size); 133 134 if (command_size == 0) { 135 // Empty command. Shouldn't happen if write was successful, fail. 136 return NULL; 137 } 138 139 // Make sure buffer has the complete contents of the command. 140 if (command_size > available_count_) { 141 if (command_size > buffer_.size()) 142 buffer_.resize((command_size / 1024 + 1) * 1024, 0); 143 if (!FillBuffer() || command_size > available_count_) { 144 // Again, assume the file was ok, and just the last chunk was lost. 145 return NULL; 146 } 147 } 148 const id_type command_id = buffer_[buffer_position_]; 149 // NOTE: command_size includes the size of the id, which is not part of 150 // the contents of the SessionCommand. 151 SessionCommand* command = 152 new SessionCommand(command_id, command_size - sizeof(id_type)); 153 if (command_size > sizeof(id_type)) { 154 memcpy(command->contents(), 155 &(buffer_[buffer_position_ + sizeof(id_type)]), 156 command_size - sizeof(id_type)); 157 } 158 buffer_position_ += command_size; 159 available_count_ -= command_size; 160 return command; 161 } 162 163 bool SessionFileReader::FillBuffer() { 164 if (available_count_ > 0 && buffer_position_ > 0) { 165 // Shift buffer to beginning. 166 memmove(&(buffer_[0]), &(buffer_[buffer_position_]), available_count_); 167 } 168 buffer_position_ = 0; 169 DCHECK(buffer_position_ + available_count_ < buffer_.size()); 170 int to_read = static_cast<int>(buffer_.size() - available_count_); 171 int read_count = file_->ReadUntilComplete(&(buffer_[available_count_]), 172 to_read); 173 if (read_count < 0) { 174 errored_ = true; 175 return false; 176 } 177 if (read_count == 0) 178 return false; 179 available_count_ += read_count; 180 return true; 181 } 182 183 } // namespace 184 185 // SessionBackend ------------------------------------------------------------- 186 187 // File names (current and previous) for a type of TAB. 188 static const char* kCurrentTabSessionFileName = "Current Tabs"; 189 static const char* kLastTabSessionFileName = "Last Tabs"; 190 191 // File names (current and previous) for a type of SESSION. 192 static const char* kCurrentSessionFileName = "Current Session"; 193 static const char* kLastSessionFileName = "Last Session"; 194 195 // static 196 const int SessionBackend::kFileReadBufferSize = 1024; 197 198 SessionBackend::SessionBackend(BaseSessionService::SessionType type, 199 const FilePath& path_to_dir) 200 : type_(type), 201 path_to_dir_(path_to_dir), 202 last_session_valid_(false), 203 inited_(false), 204 empty_file_(true) { 205 // NOTE: this is invoked on the main thread, don't do file access here. 206 } 207 208 void SessionBackend::Init() { 209 if (inited_) 210 return; 211 212 inited_ = true; 213 214 // Create the directory for session info. 215 file_util::CreateDirectory(path_to_dir_); 216 217 MoveCurrentSessionToLastSession(); 218 } 219 220 void SessionBackend::AppendCommands( 221 std::vector<SessionCommand*>* commands, 222 bool reset_first) { 223 Init(); 224 // Make sure and check current_session_file_, if opening the file failed 225 // current_session_file_ will be NULL. 226 if ((reset_first && !empty_file_) || !current_session_file_.get() || 227 !current_session_file_->IsOpen()) { 228 ResetFile(); 229 } 230 // Need to check current_session_file_ again, ResetFile may fail. 231 if (current_session_file_.get() && current_session_file_->IsOpen() && 232 !AppendCommandsToFile(current_session_file_.get(), *commands)) { 233 current_session_file_.reset(NULL); 234 } 235 empty_file_ = false; 236 STLDeleteElements(commands); 237 delete commands; 238 } 239 240 void SessionBackend::ReadLastSessionCommands( 241 scoped_refptr<BaseSessionService::InternalGetCommandsRequest> request) { 242 if (request->canceled()) 243 return; 244 Init(); 245 ReadLastSessionCommandsImpl(&(request->commands)); 246 request->ForwardResult( 247 BaseSessionService::InternalGetCommandsRequest::TupleType( 248 request->handle(), request)); 249 } 250 251 bool SessionBackend::ReadLastSessionCommandsImpl( 252 std::vector<SessionCommand*>* commands) { 253 Init(); 254 SessionFileReader file_reader(GetLastSessionPath()); 255 return file_reader.Read(type_, commands); 256 } 257 258 void SessionBackend::DeleteLastSession() { 259 Init(); 260 file_util::Delete(GetLastSessionPath(), false); 261 } 262 263 void SessionBackend::MoveCurrentSessionToLastSession() { 264 Init(); 265 current_session_file_.reset(NULL); 266 267 const FilePath current_session_path = GetCurrentSessionPath(); 268 const FilePath last_session_path = GetLastSessionPath(); 269 if (file_util::PathExists(last_session_path)) 270 file_util::Delete(last_session_path, false); 271 if (file_util::PathExists(current_session_path)) { 272 int64 file_size; 273 if (file_util::GetFileSize(current_session_path, &file_size)) { 274 if (type_ == BaseSessionService::TAB_RESTORE) { 275 UMA_HISTOGRAM_COUNTS("TabRestore.last_session_file_size", 276 static_cast<int>(file_size / 1024)); 277 } else { 278 UMA_HISTOGRAM_COUNTS("SessionRestore.last_session_file_size", 279 static_cast<int>(file_size / 1024)); 280 } 281 } 282 last_session_valid_ = file_util::Move(current_session_path, 283 last_session_path); 284 } 285 286 if (file_util::PathExists(current_session_path)) 287 file_util::Delete(current_session_path, false); 288 289 // Create and open the file for the current session. 290 ResetFile(); 291 } 292 293 void SessionBackend::ReadCurrentSessionCommands( 294 scoped_refptr<BaseSessionService::InternalGetCommandsRequest> request) { 295 if (request->canceled()) 296 return; 297 Init(); 298 ReadCurrentSessionCommandsImpl(&(request->commands)); 299 request->ForwardResult( 300 BaseSessionService::InternalGetCommandsRequest::TupleType( 301 request->handle(), request)); 302 } 303 304 bool SessionBackend::ReadCurrentSessionCommandsImpl( 305 std::vector<SessionCommand*>* commands) { 306 Init(); 307 SessionFileReader file_reader(GetCurrentSessionPath()); 308 return file_reader.Read(type_, commands); 309 } 310 311 bool SessionBackend::AppendCommandsToFile(net::FileStream* file, 312 const std::vector<SessionCommand*>& commands) { 313 for (std::vector<SessionCommand*>::const_iterator i = commands.begin(); 314 i != commands.end(); ++i) { 315 int wrote; 316 const size_type content_size = static_cast<size_type>((*i)->size()); 317 const size_type total_size = content_size + sizeof(id_type); 318 if (type_ == BaseSessionService::TAB_RESTORE) 319 UMA_HISTOGRAM_COUNTS("TabRestore.command_size", total_size); 320 else 321 UMA_HISTOGRAM_COUNTS("SessionRestore.command_size", total_size); 322 wrote = file->Write(reinterpret_cast<const char*>(&total_size), 323 sizeof(total_size), NULL); 324 if (wrote != sizeof(total_size)) { 325 NOTREACHED() << "error writing"; 326 return false; 327 } 328 id_type command_id = (*i)->id(); 329 wrote = file->Write(reinterpret_cast<char*>(&command_id), 330 sizeof(command_id), NULL); 331 if (wrote != sizeof(command_id)) { 332 NOTREACHED() << "error writing"; 333 return false; 334 } 335 if (content_size > 0) { 336 wrote = file->Write(reinterpret_cast<char*>((*i)->contents()), 337 content_size, NULL); 338 if (wrote != content_size) { 339 NOTREACHED() << "error writing"; 340 return false; 341 } 342 } 343 } 344 file->Flush(); 345 return true; 346 } 347 348 SessionBackend::~SessionBackend() { 349 } 350 351 void SessionBackend::ResetFile() { 352 DCHECK(inited_); 353 if (current_session_file_.get()) { 354 // File is already open, truncate it. We truncate instead of closing and 355 // reopening to avoid the possibility of scanners locking the file out 356 // from under us once we close it. If truncation fails, we'll try to 357 // recreate. 358 const int header_size = static_cast<int>(sizeof(FileHeader)); 359 if (current_session_file_->Truncate(header_size) != header_size) 360 current_session_file_.reset(NULL); 361 } 362 if (!current_session_file_.get()) 363 current_session_file_.reset(OpenAndWriteHeader(GetCurrentSessionPath())); 364 empty_file_ = true; 365 } 366 367 net::FileStream* SessionBackend::OpenAndWriteHeader(const FilePath& path) { 368 DCHECK(!path.empty()); 369 scoped_ptr<net::FileStream> file(new net::FileStream()); 370 if (file->Open(path, base::PLATFORM_FILE_CREATE_ALWAYS | 371 base::PLATFORM_FILE_WRITE | base::PLATFORM_FILE_EXCLUSIVE_WRITE | 372 base::PLATFORM_FILE_EXCLUSIVE_READ) != net::OK) 373 return NULL; 374 FileHeader header; 375 header.signature = kFileSignature; 376 header.version = kFileCurrentVersion; 377 int wrote = file->Write(reinterpret_cast<char*>(&header), 378 sizeof(header), NULL); 379 if (wrote != sizeof(header)) 380 return NULL; 381 return file.release(); 382 } 383 384 FilePath SessionBackend::GetLastSessionPath() { 385 FilePath path = path_to_dir_; 386 if (type_ == BaseSessionService::TAB_RESTORE) 387 path = path.AppendASCII(kLastTabSessionFileName); 388 else 389 path = path.AppendASCII(kLastSessionFileName); 390 return path; 391 } 392 393 FilePath SessionBackend::GetCurrentSessionPath() { 394 FilePath path = path_to_dir_; 395 if (type_ == BaseSessionService::TAB_RESTORE) 396 path = path.AppendASCII(kCurrentTabSessionFileName); 397 else 398 path = path.AppendASCII(kCurrentSessionFileName); 399 return path; 400 } 401