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