Home | History | Annotate | Download | only in sessions
      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   file_util::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 (file_util::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