• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
SessionFileReader(const FilePath & path)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 
Read(BaseSessionService::SessionType type,std::vector<SessionCommand * > * commands)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 
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       // 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 
FillBuffer()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 
SessionBackend(BaseSessionService::SessionType type,const FilePath & path_to_dir)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 
Init()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 
AppendCommands(std::vector<SessionCommand * > * commands,bool reset_first)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 
ReadLastSessionCommands(scoped_refptr<BaseSessionService::InternalGetCommandsRequest> request)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 
ReadLastSessionCommandsImpl(std::vector<SessionCommand * > * commands)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 
DeleteLastSession()258 void SessionBackend::DeleteLastSession() {
259   Init();
260   file_util::Delete(GetLastSessionPath(), false);
261 }
262 
MoveCurrentSessionToLastSession()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 
ReadCurrentSessionCommands(scoped_refptr<BaseSessionService::InternalGetCommandsRequest> request)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 
ReadCurrentSessionCommandsImpl(std::vector<SessionCommand * > * commands)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 
AppendCommandsToFile(net::FileStream * file,const std::vector<SessionCommand * > & commands)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 
~SessionBackend()348 SessionBackend::~SessionBackend() {
349 }
350 
ResetFile()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 
OpenAndWriteHeader(const FilePath & path)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 
GetLastSessionPath()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 
GetCurrentSessionPath()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