• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SessionFileReader(const base::FilePath & path)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 
Read(BaseSessionService::SessionType type,std::vector<SessionCommand * > * commands)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 
ReadCommand()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 
FillBuffer()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 
SessionBackend(BaseSessionService::SessionType type,const base::FilePath & path_to_dir)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 
Init()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 
AppendCommands(std::vector<SessionCommand * > * commands,bool reset_first)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 
ReadLastSessionCommands(const base::CancelableTaskTracker::IsCanceledCallback & is_canceled,const BaseSessionService::InternalGetCommandsCallback & callback)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 
ReadLastSessionCommandsImpl(std::vector<SessionCommand * > * commands)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 
DeleteLastSession()263 void SessionBackend::DeleteLastSession() {
264   Init();
265   base::DeleteFile(GetLastSessionPath(), false);
266 }
267 
MoveCurrentSessionToLastSession()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 
ReadCurrentSessionCommandsImpl(std::vector<SessionCommand * > * commands)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 
AppendCommandsToFile(base::File * file,const std::vector<SessionCommand * > & commands)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 
~SessionBackend()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 
ResetFile()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 
OpenAndWriteHeader(const base::FilePath & path)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 
GetLastSessionPath()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 
GetCurrentSessionPath()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