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
15using base::TimeTicks;
16
17// File version number.
18static const int32 kFileCurrentVersion = 1;
19
20// The signature at the beginning of the file = SSNS (Sessions).
21static const int32 kFileSignature = 0x53534E53;
22
23namespace {
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.
27struct 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
38class 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
88bool 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
117SessionCommand* 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
163bool 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.
188static const char* kCurrentTabSessionFileName = "Current Tabs";
189static const char* kLastTabSessionFileName = "Last Tabs";
190
191// File names (current and previous) for a type of SESSION.
192static const char* kCurrentSessionFileName = "Current Session";
193static const char* kLastSessionFileName = "Last Session";
194
195// static
196const int SessionBackend::kFileReadBufferSize = 1024;
197
198SessionBackend::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
208void 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
220void 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
240void 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
251bool SessionBackend::ReadLastSessionCommandsImpl(
252    std::vector<SessionCommand*>* commands) {
253  Init();
254  SessionFileReader file_reader(GetLastSessionPath());
255  return file_reader.Read(type_, commands);
256}
257
258void SessionBackend::DeleteLastSession() {
259  Init();
260  file_util::Delete(GetLastSessionPath(), false);
261}
262
263void 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
293void 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
304bool SessionBackend::ReadCurrentSessionCommandsImpl(
305    std::vector<SessionCommand*>* commands) {
306  Init();
307  SessionFileReader file_reader(GetCurrentSessionPath());
308  return file_reader.Read(type_, commands);
309}
310
311bool 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
348SessionBackend::~SessionBackend() {
349}
350
351void 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
367net::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
384FilePath 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
393FilePath 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