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/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