OLD | NEW |
| (Empty) |
1 // Copyright 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 "components/sessions/session_backend.h" | |
6 | |
7 #include <limits> | |
8 | |
9 #include "base/files/file.h" | |
10 #include "base/files/file_util.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 namespace sessions { | |
18 | |
19 // File version number. | |
20 static const int32 kFileCurrentVersion = 1; | |
21 | |
22 // The signature at the beginning of the file = SSNS (Sessions). | |
23 static const int32 kFileSignature = 0x53534E53; | |
24 | |
25 namespace { | |
26 | |
27 // The file header is the first bytes written to the file, | |
28 // and is used to identify the file as one written by us. | |
29 struct FileHeader { | |
30 int32 signature; | |
31 int32 version; | |
32 }; | |
33 | |
34 // SessionFileReader ---------------------------------------------------------- | |
35 | |
36 // SessionFileReader is responsible for reading the set of SessionCommands that | |
37 // describe a Session back from a file. SessionFileRead does minimal error | |
38 // checking on the file (pretty much only that the header is valid). | |
39 | |
40 class SessionFileReader { | |
41 public: | |
42 typedef sessions::SessionCommand::id_type id_type; | |
43 typedef sessions::SessionCommand::size_type size_type; | |
44 | |
45 explicit SessionFileReader(const base::FilePath& path) | |
46 : errored_(false), | |
47 buffer_(SessionBackend::kFileReadBufferSize, 0), | |
48 buffer_position_(0), | |
49 available_count_(0) { | |
50 file_.reset(new base::File( | |
51 path, base::File::FLAG_OPEN | base::File::FLAG_READ)); | |
52 } | |
53 // Reads the contents of the file specified in the constructor, returning | |
54 // true on success. It is up to the caller to free all SessionCommands | |
55 // added to commands. | |
56 bool Read(sessions::BaseSessionService::SessionType type, | |
57 ScopedVector<sessions::SessionCommand>* commands); | |
58 | |
59 private: | |
60 // Reads a single command, returning it. A return value of NULL indicates | |
61 // either there are no commands, or there was an error. Use errored_ to | |
62 // distinguish the two. If NULL is returned, and there is no error, it means | |
63 // the end of file was successfully reached. | |
64 sessions::SessionCommand* ReadCommand(); | |
65 | |
66 // Shifts the unused portion of buffer_ to the beginning and fills the | |
67 // remaining portion with data from the file. Returns false if the buffer | |
68 // couldn't be filled. A return value of false only signals an error if | |
69 // errored_ is set to true. | |
70 bool FillBuffer(); | |
71 | |
72 // Whether an error condition has been detected ( | |
73 bool errored_; | |
74 | |
75 // As we read from the file, data goes here. | |
76 std::string buffer_; | |
77 | |
78 // The file. | |
79 scoped_ptr<base::File> file_; | |
80 | |
81 // Position in buffer_ of the data. | |
82 size_t buffer_position_; | |
83 | |
84 // Number of available bytes; relative to buffer_position_. | |
85 size_t available_count_; | |
86 | |
87 DISALLOW_COPY_AND_ASSIGN(SessionFileReader); | |
88 }; | |
89 | |
90 bool SessionFileReader::Read(sessions::BaseSessionService::SessionType type, | |
91 ScopedVector<sessions::SessionCommand>* commands) { | |
92 if (!file_->IsValid()) | |
93 return false; | |
94 FileHeader header; | |
95 int read_count; | |
96 TimeTicks start_time = TimeTicks::Now(); | |
97 read_count = file_->ReadAtCurrentPos(reinterpret_cast<char*>(&header), | |
98 sizeof(header)); | |
99 if (read_count != sizeof(header) || header.signature != kFileSignature || | |
100 header.version != kFileCurrentVersion) | |
101 return false; | |
102 | |
103 ScopedVector<sessions::SessionCommand> read_commands; | |
104 for (sessions::SessionCommand* command = ReadCommand(); command && !errored_; | |
105 command = ReadCommand()) | |
106 read_commands.push_back(command); | |
107 if (!errored_) | |
108 read_commands.swap(*commands); | |
109 if (type == sessions::BaseSessionService::TAB_RESTORE) { | |
110 UMA_HISTOGRAM_TIMES("TabRestore.read_session_file_time", | |
111 TimeTicks::Now() - start_time); | |
112 } else { | |
113 UMA_HISTOGRAM_TIMES("SessionRestore.read_session_file_time", | |
114 TimeTicks::Now() - start_time); | |
115 } | |
116 return !errored_; | |
117 } | |
118 | |
119 sessions::SessionCommand* SessionFileReader::ReadCommand() { | |
120 // Make sure there is enough in the buffer for the size of the next command. | |
121 if (available_count_ < sizeof(size_type)) { | |
122 if (!FillBuffer()) | |
123 return NULL; | |
124 if (available_count_ < sizeof(size_type)) { | |
125 VLOG(1) << "SessionFileReader::ReadCommand, file incomplete"; | |
126 // Still couldn't read a valid size for the command, assume write was | |
127 // incomplete and return NULL. | |
128 return NULL; | |
129 } | |
130 } | |
131 // Get the size of the command. | |
132 size_type command_size; | |
133 memcpy(&command_size, &(buffer_[buffer_position_]), sizeof(command_size)); | |
134 buffer_position_ += sizeof(command_size); | |
135 available_count_ -= sizeof(command_size); | |
136 | |
137 if (command_size == 0) { | |
138 VLOG(1) << "SessionFileReader::ReadCommand, empty command"; | |
139 // Empty command. Shouldn't happen if write was successful, fail. | |
140 return NULL; | |
141 } | |
142 | |
143 // Make sure buffer has the complete contents of the command. | |
144 if (command_size > available_count_) { | |
145 if (command_size > buffer_.size()) | |
146 buffer_.resize((command_size / 1024 + 1) * 1024, 0); | |
147 if (!FillBuffer() || command_size > available_count_) { | |
148 // Again, assume the file was ok, and just the last chunk was lost. | |
149 VLOG(1) << "SessionFileReader::ReadCommand, last chunk lost"; | |
150 return NULL; | |
151 } | |
152 } | |
153 const id_type command_id = buffer_[buffer_position_]; | |
154 // NOTE: command_size includes the size of the id, which is not part of | |
155 // the contents of the SessionCommand. | |
156 sessions::SessionCommand* command = | |
157 new sessions::SessionCommand(command_id, command_size - sizeof(id_type)); | |
158 if (command_size > sizeof(id_type)) { | |
159 memcpy(command->contents(), | |
160 &(buffer_[buffer_position_ + sizeof(id_type)]), | |
161 command_size - sizeof(id_type)); | |
162 } | |
163 buffer_position_ += command_size; | |
164 available_count_ -= command_size; | |
165 return command; | |
166 } | |
167 | |
168 bool SessionFileReader::FillBuffer() { | |
169 if (available_count_ > 0 && buffer_position_ > 0) { | |
170 // Shift buffer to beginning. | |
171 memmove(&(buffer_[0]), &(buffer_[buffer_position_]), available_count_); | |
172 } | |
173 buffer_position_ = 0; | |
174 DCHECK(buffer_position_ + available_count_ < buffer_.size()); | |
175 int to_read = static_cast<int>(buffer_.size() - available_count_); | |
176 int read_count = file_->ReadAtCurrentPos(&(buffer_[available_count_]), | |
177 to_read); | |
178 if (read_count < 0) { | |
179 errored_ = true; | |
180 return false; | |
181 } | |
182 if (read_count == 0) | |
183 return false; | |
184 available_count_ += read_count; | |
185 return true; | |
186 } | |
187 | |
188 } // namespace | |
189 | |
190 // SessionBackend ------------------------------------------------------------- | |
191 | |
192 // File names (current and previous) for a type of TAB. | |
193 static const char* kCurrentTabSessionFileName = "Current Tabs"; | |
194 static const char* kLastTabSessionFileName = "Last Tabs"; | |
195 | |
196 // File names (current and previous) for a type of SESSION. | |
197 static const char* kCurrentSessionFileName = "Current Session"; | |
198 static const char* kLastSessionFileName = "Last Session"; | |
199 | |
200 // static | |
201 const int SessionBackend::kFileReadBufferSize = 1024; | |
202 | |
203 SessionBackend::SessionBackend(sessions::BaseSessionService::SessionType type, | |
204 const base::FilePath& path_to_dir) | |
205 : type_(type), | |
206 path_to_dir_(path_to_dir), | |
207 last_session_valid_(false), | |
208 inited_(false), | |
209 empty_file_(true) { | |
210 // NOTE: this is invoked on the main thread, don't do file access here. | |
211 } | |
212 | |
213 void SessionBackend::Init() { | |
214 if (inited_) | |
215 return; | |
216 | |
217 inited_ = true; | |
218 | |
219 // Create the directory for session info. | |
220 base::CreateDirectory(path_to_dir_); | |
221 | |
222 MoveCurrentSessionToLastSession(); | |
223 } | |
224 | |
225 void SessionBackend::AppendCommands( | |
226 ScopedVector<sessions::SessionCommand> commands, | |
227 bool reset_first) { | |
228 Init(); | |
229 // Make sure and check current_session_file_, if opening the file failed | |
230 // current_session_file_ will be NULL. | |
231 if ((reset_first && !empty_file_) || !current_session_file_.get() || | |
232 !current_session_file_->IsValid()) { | |
233 ResetFile(); | |
234 } | |
235 // Need to check current_session_file_ again, ResetFile may fail. | |
236 if (current_session_file_.get() && current_session_file_->IsValid() && | |
237 !AppendCommandsToFile(current_session_file_.get(), commands)) { | |
238 current_session_file_.reset(NULL); | |
239 } | |
240 empty_file_ = false; | |
241 } | |
242 | |
243 void SessionBackend::ReadLastSessionCommands( | |
244 const base::CancelableTaskTracker::IsCanceledCallback& is_canceled, | |
245 const sessions::BaseSessionService::GetCommandsCallback& callback) { | |
246 if (is_canceled.Run()) | |
247 return; | |
248 | |
249 Init(); | |
250 | |
251 ScopedVector<sessions::SessionCommand> commands; | |
252 ReadLastSessionCommandsImpl(&commands); | |
253 callback.Run(commands.Pass()); | |
254 } | |
255 | |
256 bool SessionBackend::ReadLastSessionCommandsImpl( | |
257 ScopedVector<sessions::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_ == sessions::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 ScopedVector<sessions::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 ScopedVector<sessions::SessionCommand>& commands) { | |
306 for (ScopedVector<sessions::SessionCommand>::const_iterator i = | |
307 commands.begin(); | |
308 i != commands.end(); ++i) { | |
309 int wrote; | |
310 const size_type content_size = static_cast<size_type>((*i)->size()); | |
311 const size_type total_size = content_size + sizeof(id_type); | |
312 if (type_ == sessions::BaseSessionService::TAB_RESTORE) | |
313 UMA_HISTOGRAM_COUNTS("TabRestore.command_size", total_size); | |
314 else | |
315 UMA_HISTOGRAM_COUNTS("SessionRestore.command_size", total_size); | |
316 wrote = file->WriteAtCurrentPos(reinterpret_cast<const char*>(&total_size), | |
317 sizeof(total_size)); | |
318 if (wrote != sizeof(total_size)) { | |
319 NOTREACHED() << "error writing"; | |
320 return false; | |
321 } | |
322 id_type command_id = (*i)->id(); | |
323 wrote = file->WriteAtCurrentPos(reinterpret_cast<char*>(&command_id), | |
324 sizeof(command_id)); | |
325 if (wrote != sizeof(command_id)) { | |
326 NOTREACHED() << "error writing"; | |
327 return false; | |
328 } | |
329 if (content_size > 0) { | |
330 wrote = file->WriteAtCurrentPos(reinterpret_cast<char*>((*i)->contents()), | |
331 content_size); | |
332 if (wrote != content_size) { | |
333 NOTREACHED() << "error writing"; | |
334 return false; | |
335 } | |
336 } | |
337 } | |
338 #if defined(OS_CHROMEOS) | |
339 file->Flush(); | |
340 #endif | |
341 return true; | |
342 } | |
343 | |
344 SessionBackend::~SessionBackend() { | |
345 if (current_session_file_.get()) { | |
346 // Destructor performs file IO because file is open in sync mode. | |
347 // crbug.com/112512. | |
348 base::ThreadRestrictions::ScopedAllowIO allow_io; | |
349 current_session_file_.reset(); | |
350 } | |
351 } | |
352 | |
353 void SessionBackend::ResetFile() { | |
354 DCHECK(inited_); | |
355 if (current_session_file_.get()) { | |
356 // File is already open, truncate it. We truncate instead of closing and | |
357 // reopening to avoid the possibility of scanners locking the file out | |
358 // from under us once we close it. If truncation fails, we'll try to | |
359 // recreate. | |
360 const int header_size = static_cast<int>(sizeof(FileHeader)); | |
361 if (current_session_file_->Seek( | |
362 base::File::FROM_BEGIN, header_size) != header_size || | |
363 !current_session_file_->SetLength(header_size)) | |
364 current_session_file_.reset(NULL); | |
365 } | |
366 if (!current_session_file_.get()) | |
367 current_session_file_.reset(OpenAndWriteHeader(GetCurrentSessionPath())); | |
368 empty_file_ = true; | |
369 } | |
370 | |
371 base::File* SessionBackend::OpenAndWriteHeader(const base::FilePath& path) { | |
372 DCHECK(!path.empty()); | |
373 scoped_ptr<base::File> file(new base::File( | |
374 path, | |
375 base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE | | |
376 base::File::FLAG_EXCLUSIVE_WRITE | base::File::FLAG_EXCLUSIVE_READ)); | |
377 if (!file->IsValid()) | |
378 return NULL; | |
379 FileHeader header; | |
380 header.signature = kFileSignature; | |
381 header.version = kFileCurrentVersion; | |
382 int wrote = file->WriteAtCurrentPos(reinterpret_cast<char*>(&header), | |
383 sizeof(header)); | |
384 if (wrote != sizeof(header)) | |
385 return NULL; | |
386 return file.release(); | |
387 } | |
388 | |
389 base::FilePath SessionBackend::GetLastSessionPath() { | |
390 base::FilePath path = path_to_dir_; | |
391 if (type_ == sessions::BaseSessionService::TAB_RESTORE) | |
392 path = path.AppendASCII(kLastTabSessionFileName); | |
393 else | |
394 path = path.AppendASCII(kLastSessionFileName); | |
395 return path; | |
396 } | |
397 | |
398 base::FilePath SessionBackend::GetCurrentSessionPath() { | |
399 base::FilePath path = path_to_dir_; | |
400 if (type_ == sessions::BaseSessionService::TAB_RESTORE) | |
401 path = path.AppendASCII(kCurrentTabSessionFileName); | |
402 else | |
403 path = path.AppendASCII(kCurrentSessionFileName); | |
404 return path; | |
405 } | |
406 | |
407 } // namespace sessions | |
OLD | NEW |