OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium OS 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 "volume.h" | |
6 | |
7 #include <cstring> | |
8 #include <sstream> | |
9 | |
10 #include "request.h" | |
11 #include "volume_archive_libarchive.h" | |
12 #include "volume_reader_javascript_stream.h" | |
13 | |
14 namespace { | |
15 | |
16 #define LOG(x) \ | |
17 do { \ | |
18 std::stringstream fmt; \ | |
19 fmt << x; \ | |
20 message_sender_->CONSOLE_LOG(file_system_id_, request_id, fmt.str()); \ | |
21 } while (0) | |
22 | |
23 typedef std::map<std::string, VolumeArchive*>::const_iterator | |
24 volume_archive_iterator; | |
25 | |
26 const char kPathDelimiter[] = "/"; | |
27 | |
28 // size is int64_t and modification_time is time_t because this is how | |
29 // libarchive is going to pass them to us. | |
30 pp::VarDictionary CreateEntry(int64_t index, | |
31 const std::string& name, | |
32 bool is_directory, | |
33 int64_t size, | |
34 time_t modification_time) { | |
35 pp::VarDictionary entry_metadata; | |
36 // index is int64_t, unsupported by pp::Var | |
37 std::stringstream ss_index; | |
38 ss_index << index; | |
39 entry_metadata.Set("index", ss_index.str()); | |
40 entry_metadata.Set("isDirectory", is_directory); | |
41 entry_metadata.Set("name", name); | |
42 // size is int64_t, unsupported by pp::Var | |
43 std::stringstream ss_size; | |
44 ss_size << size; | |
45 entry_metadata.Set("size", ss_size.str()); | |
46 // mtime is time_t, unsupported by pp::Var | |
47 std::stringstream ss_modification_time; | |
48 ss_modification_time << modification_time; | |
49 entry_metadata.Set("modificationTime", ss_modification_time.str()); | |
50 | |
51 if (is_directory) | |
52 entry_metadata.Set("entries", pp::VarDictionary()); | |
53 | |
54 return entry_metadata; | |
55 } | |
56 | |
57 void ConstructMetadata(int64_t index, | |
58 const std::string& entry_path, | |
59 int64_t size, | |
60 bool is_directory, | |
61 time_t modification_time, | |
62 pp::VarDictionary* parent_metadata) { | |
63 if (entry_path == "") | |
64 return; | |
65 | |
66 pp::VarDictionary parent_entries = | |
67 pp::VarDictionary(parent_metadata->Get("entries")); | |
68 | |
69 unsigned int position = entry_path.find(kPathDelimiter); | |
70 pp::VarDictionary entry_metadata; | |
71 std::string entry_name; | |
72 | |
73 if (position == std::string::npos) { // The entry itself. | |
74 entry_name = entry_path; | |
75 entry_metadata = | |
76 CreateEntry(index, entry_name, is_directory, size, modification_time); | |
77 | |
78 // Update directory information. Required as sometimes the directory itself | |
79 // is returned after the files inside it. | |
80 pp::Var old_entry_metadata_var = parent_entries.Get(entry_name); | |
81 if (!old_entry_metadata_var.is_undefined()) { | |
82 pp::VarDictionary old_entry_metadata = | |
83 pp::VarDictionary(old_entry_metadata_var); | |
84 PP_DCHECK(old_entry_metadata.Get("isDirectory").AsBool()); | |
85 entry_metadata.Set("entries", old_entry_metadata.Get("entries")); | |
86 } | |
87 } else { // Get next parent on the way to the entry. | |
88 entry_name = entry_path.substr(0, position); | |
89 | |
90 // Get next parent metadata. If none, create a new directory entry for it. | |
91 // Some archives don't have directory information inside and for some the | |
92 // information is returned later than the files inside it. | |
93 pp::Var entry_metadata_var = parent_entries.Get(entry_name); | |
94 if (entry_metadata_var.is_undefined()) | |
95 entry_metadata = CreateEntry(-1, entry_name, true, 0, modification_time); | |
96 else | |
97 entry_metadata = pp::VarDictionary(parent_entries.Get(entry_name)); | |
98 | |
99 // Continue to construct metadata for all directories on the path to the | |
100 // to the entry and for the entry itself. | |
101 std::string entry_path_without_next_parent = entry_path.substr( | |
102 position + sizeof(kPathDelimiter) - 1 /* Last char is '\0'. */); | |
103 | |
104 ConstructMetadata(index, | |
105 entry_path_without_next_parent, | |
106 size, | |
107 is_directory, | |
108 modification_time, | |
109 &entry_metadata); | |
110 } | |
111 | |
112 // Recreate parent_metadata. This is necessary because pp::VarDictionary::Get | |
113 // returns a Var, not a Var& or Var* to directly modify the result. | |
114 parent_entries.Set(entry_name, entry_metadata); | |
115 parent_metadata->Set("entries", parent_entries); | |
116 } | |
117 | |
118 // An internal implementation of JavaScriptRequestorInterface. | |
119 class JavaScriptRequestor : public JavaScriptRequestorInterface { | |
120 public: | |
121 // JavaScriptRequestor does not own the volume pointer. | |
122 explicit JavaScriptRequestor(Volume* volume) : volume_(volume) {} | |
123 | |
124 virtual void RequestFileChunk(const std::string& request_id, | |
125 int64_t offset, | |
126 int64_t bytes_to_read) { | |
127 PP_DCHECK(offset >= 0); | |
128 PP_DCHECK(bytes_to_read > 0); | |
129 volume_->message_sender()->SendFileChunkRequest( | |
130 volume_->file_system_id(), request_id, offset, bytes_to_read); | |
131 } | |
132 | |
133 virtual void RequestPassphrase(const std::string& request_id) { | |
134 volume_->message_sender()->SendPassphraseRequest( | |
135 volume_->file_system_id(), request_id); | |
136 } | |
137 | |
138 private: | |
139 Volume* volume_; | |
140 }; | |
141 | |
142 // An internal implementation of VolumeArchiveFactoryInterface for default | |
143 // Volume constructor. | |
144 class VolumeArchiveFactory : public VolumeArchiveFactoryInterface { | |
145 public: | |
146 virtual VolumeArchive* Create(VolumeReader* reader) { | |
147 return new VolumeArchiveLibarchive(reader); | |
148 } | |
149 }; | |
150 | |
151 // An internal implementation of VolumeReaderFactoryInterface for default Volume | |
152 // constructor. | |
153 class VolumeReaderFactory : public VolumeReaderFactoryInterface { | |
154 public: | |
155 // VolumeReaderFactory does not own the volume pointer. | |
156 explicit VolumeReaderFactory(Volume* volume) : volume_(volume) {} | |
157 | |
158 virtual VolumeReader* Create(int64_t archive_size) { | |
159 return new VolumeReaderJavaScriptStream(archive_size, volume_->requestor()); | |
160 } | |
161 | |
162 private: | |
163 Volume* volume_; | |
164 }; | |
165 | |
166 } // namespace | |
167 | |
168 struct Volume::OpenFileArgs { | |
169 OpenFileArgs(const std::string& request_id, | |
170 int64_t index, | |
171 const std::string& encoding, | |
172 int64_t archive_size) : request_id(request_id), | |
173 index(index), | |
174 encoding(encoding), | |
175 archive_size(archive_size) {} | |
176 const std::string request_id; | |
177 const int64_t index; | |
178 const std::string encoding; | |
179 const int64_t archive_size; | |
180 }; | |
181 | |
182 Volume::Volume(const pp::InstanceHandle& instance_handle, | |
183 const std::string& file_system_id, | |
184 JavaScriptMessageSenderInterface* message_sender) | |
185 : volume_archive_(NULL), | |
186 file_system_id_(file_system_id), | |
187 message_sender_(message_sender), | |
188 worker_(instance_handle), | |
189 callback_factory_(this) { | |
190 requestor_ = new JavaScriptRequestor(this); | |
191 volume_archive_factory_ = new VolumeArchiveFactory(); | |
192 volume_reader_factory_ = new VolumeReaderFactory(this); | |
193 // Delegating constructors only from c++11. | |
194 } | |
195 | |
196 Volume::Volume(const pp::InstanceHandle& instance_handle, | |
197 const std::string& file_system_id, | |
198 JavaScriptMessageSenderInterface* message_sender, | |
199 VolumeArchiveFactoryInterface* volume_archive_factory, | |
200 VolumeReaderFactoryInterface* volume_reader_factory) | |
201 : volume_archive_(NULL), | |
202 file_system_id_(file_system_id), | |
203 message_sender_(message_sender), | |
204 worker_(instance_handle), | |
205 callback_factory_(this), | |
206 volume_archive_factory_(volume_archive_factory), | |
207 volume_reader_factory_(volume_reader_factory) { | |
208 requestor_ = new JavaScriptRequestor(this); | |
209 } | |
210 | |
211 Volume::~Volume() { | |
212 worker_.Join(); | |
213 | |
214 if (volume_archive_) { | |
215 volume_archive_->Cleanup(); | |
216 delete volume_archive_; | |
217 } | |
218 | |
219 delete requestor_; | |
220 delete volume_archive_factory_; | |
221 delete volume_reader_factory_; | |
222 } | |
223 | |
224 bool Volume::Init() { | |
225 return worker_.Start(); | |
226 } | |
227 | |
228 void Volume::ReadMetadata(const std::string& request_id, | |
229 const std::string& encoding, | |
230 int64_t archive_size) { | |
231 worker_.message_loop().PostWork(callback_factory_.NewCallback( | |
232 &Volume::ReadMetadataCallback, request_id, encoding, archive_size)); | |
233 } | |
234 | |
235 void Volume::OpenFile(const std::string& request_id, | |
236 int64_t index, | |
237 const std::string& encoding, | |
238 int64_t archive_size) { | |
239 worker_.message_loop().PostWork(callback_factory_.NewCallback( | |
240 &Volume::OpenFileCallback, OpenFileArgs(request_id, index, encoding, | |
241 archive_size))); | |
242 } | |
243 | |
244 void Volume::CloseFile(const std::string& request_id, | |
245 const std::string& open_request_id) { | |
246 // Though close file could be executed on main thread, we send it to worker_ | |
247 // in order to ensure thread safety. | |
248 worker_.message_loop().PostWork(callback_factory_.NewCallback( | |
249 &Volume::CloseFileCallback, request_id, open_request_id)); | |
250 } | |
251 | |
252 void Volume::ReadFile(const std::string& request_id, | |
253 const pp::VarDictionary& dictionary) { | |
254 worker_.message_loop().PostWork(callback_factory_.NewCallback( | |
255 &Volume::ReadFileCallback, request_id, dictionary)); | |
256 } | |
257 | |
258 void Volume::ReadChunkDone(const std::string& request_id, | |
259 const pp::VarArrayBuffer& array_buffer, | |
260 int64_t read_offset) { | |
261 PP_DCHECK(volume_archive_); | |
262 | |
263 job_lock_.Acquire(); | |
264 if (request_id == reader_request_id_) { | |
265 static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader())-> | |
266 SetBufferAndSignal(array_buffer, read_offset); | |
267 } | |
268 job_lock_.Release(); | |
269 } | |
270 | |
271 void Volume::ReadChunkError(const std::string& request_id) { | |
272 PP_DCHECK(volume_archive_); | |
273 job_lock_.Acquire(); | |
274 if (request_id == reader_request_id_) { | |
275 static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader())-> | |
276 ReadErrorSignal(); | |
277 } | |
278 job_lock_.Release(); | |
279 } | |
280 | |
281 void Volume::ReadPassphraseDone(const std::string& request_id, | |
282 const std::string& passphrase) { | |
283 PP_DCHECK(volume_archive_); | |
284 | |
285 job_lock_.Acquire(); | |
286 if (request_id == reader_request_id_) { | |
287 static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader())-> | |
288 SetPassphraseAndSignal(passphrase); | |
289 } | |
290 job_lock_.Release(); | |
291 } | |
292 | |
293 void Volume::ReadPassphraseError(const std::string& request_id) { | |
294 PP_DCHECK(volume_archive_); | |
295 | |
296 job_lock_.Acquire(); | |
297 if (request_id == reader_request_id_) { | |
298 static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader())-> | |
299 PassphraseErrorSignal(); | |
300 } | |
301 job_lock_.Release(); | |
302 } | |
303 | |
304 void Volume::ReadMetadataCallback(int32_t /*result*/, | |
305 const std::string& request_id, | |
306 const std::string& encoding, | |
307 int64_t archive_size) { | |
308 if (volume_archive_) { | |
309 message_sender_->SendFileSystemError( | |
310 file_system_id_, request_id, "ALREADY_OPENED"); | |
311 } | |
312 | |
313 job_lock_.Acquire(); | |
314 volume_archive_ = volume_archive_factory_->Create( | |
315 volume_reader_factory_->Create(archive_size)); | |
316 static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader())-> | |
317 SetRequestId(request_id); | |
318 reader_request_id_ = request_id; | |
319 job_lock_.Release(); | |
320 | |
321 if (!volume_archive_->Init(encoding)) { | |
322 message_sender_->SendFileSystemError( | |
323 file_system_id_, request_id, volume_archive_->error_message()); | |
324 ClearJob(); | |
325 delete volume_archive_; | |
326 volume_archive_ = NULL; | |
327 return; | |
328 } | |
329 | |
330 // Read and construct metadata. | |
331 pp::VarDictionary root_metadata = CreateEntry(-1, "" /* name */, true, 0, 0); | |
332 | |
333 const char* path_name = NULL; | |
334 int64_t size = 0; | |
335 bool is_directory = false; | |
336 time_t modification_time = 0; | |
337 int64_t index = 0; | |
338 | |
339 for (;;) { | |
340 VolumeArchive::Result ret = volume_archive_->GetNextHeader( | |
341 &path_name, &size, &is_directory, &modification_time); | |
342 if (ret == VolumeArchive::RESULT_FAIL) { | |
343 message_sender_->SendFileSystemError( | |
344 file_system_id_, request_id, volume_archive_->error_message()); | |
345 ClearJob(); | |
346 delete volume_archive_; | |
347 volume_archive_ = NULL; | |
348 return; | |
349 } else if (ret == VolumeArchive::RESULT_EOF) | |
350 break; | |
351 | |
352 ConstructMetadata(index, path_name, size, is_directory, modification_time, | |
353 &root_metadata); | |
354 | |
355 ++index; | |
356 } | |
357 | |
358 ClearJob(); | |
359 | |
360 // Send metadata back to JavaScript. | |
361 message_sender_->SendReadMetadataDone( | |
362 file_system_id_, request_id, root_metadata); | |
363 } | |
364 | |
365 void Volume::OpenFileCallback(int32_t /*result*/, | |
366 const OpenFileArgs& args) { | |
367 if (!volume_archive_) { | |
368 message_sender_->SendFileSystemError( | |
369 file_system_id_, args.request_id, "NOT_OPENED"); | |
370 return; | |
371 } | |
372 | |
373 job_lock_.Acquire(); | |
374 if (!reader_request_id_.empty()) { | |
375 // It is illegal to open a file while another operation is in progress or | |
376 // another file is opened. | |
377 message_sender_->SendFileSystemError( | |
378 file_system_id_, args.request_id, "ILLEGAL"); | |
379 job_lock_.Release(); | |
380 return; | |
381 } | |
382 static_cast<VolumeReaderJavaScriptStream*>(volume_archive_->reader())-> | |
383 SetRequestId(args.request_id); | |
384 reader_request_id_ = args.request_id; | |
385 job_lock_.Release(); | |
386 | |
387 if (!volume_archive_->SeekHeader(args.index)) { | |
388 message_sender_->SendFileSystemError( | |
389 file_system_id_, args.request_id, volume_archive_->error_message()); | |
390 ClearJob(); | |
391 return; | |
392 } | |
393 | |
394 if (volume_archive_->GetNextHeader() == VolumeArchive::RESULT_FAIL) { | |
395 message_sender_->SendFileSystemError( | |
396 file_system_id_, args.request_id, volume_archive_->error_message()); | |
397 ClearJob(); | |
398 return; | |
399 } | |
400 | |
401 // Send successful opened file response to NaCl. | |
402 message_sender_->SendOpenFileDone(file_system_id_, args.request_id); | |
403 } | |
404 | |
405 void Volume::CloseFileCallback(int32_t /*result*/, | |
406 const std::string& request_id, | |
407 const std::string& open_request_id) { | |
408 job_lock_.Acquire(); | |
409 reader_request_id_ = ""; | |
410 job_lock_.Release(); | |
411 | |
412 message_sender_->SendCloseFileDone( | |
413 file_system_id_, request_id, open_request_id); | |
414 } | |
415 | |
416 void Volume::ReadFileCallback(int32_t /*result*/, | |
417 const std::string& request_id, | |
418 const pp::VarDictionary& dictionary) { | |
419 if (!volume_archive_) { | |
420 message_sender_->SendFileSystemError( | |
421 file_system_id_, request_id, "NOT_OPENED"); | |
422 return; | |
423 } | |
424 | |
425 std::string open_request_id( | |
426 dictionary.Get(request::key::kOpenRequestId).AsString()); | |
427 int64_t offset = | |
428 request::GetInt64FromString(dictionary, request::key::kOffset); | |
429 int64_t length = | |
430 request::GetInt64FromString(dictionary, request::key::kLength); | |
431 PP_DCHECK(length > 0); // JavaScript must not make requests with length <= 0. | |
432 | |
433 job_lock_.Acquire(); | |
434 if (open_request_id != reader_request_id_) { | |
435 // The file is not opened. | |
436 message_sender_->SendFileSystemError( | |
437 file_system_id_, request_id, "FILE_NOT_OPENED"); | |
438 job_lock_.Release(); | |
439 return; | |
440 } | |
441 job_lock_.Release(); | |
442 | |
443 // Decompress data and send it to JavaScript. Sending data is done in chunks | |
444 // depending on how many bytes VolumeArchive::ReadData returns. | |
445 int64_t left_length = length; | |
446 while (left_length > 0) { | |
447 const char* destination_buffer = NULL; | |
448 int64_t read_bytes = volume_archive_->ReadData( | |
449 offset, left_length, &destination_buffer); | |
450 | |
451 if (read_bytes < 0) { | |
452 // Error messages should be sent to the read request (request_id), not | |
453 // open request (open_request_id), as the last one has finished and this | |
454 // is a read file. | |
455 message_sender_->SendFileSystemError( | |
456 file_system_id_, request_id, volume_archive_->error_message()); | |
457 | |
458 // Should not cleanup VolumeArchive as Volume::CloseFile will be called in | |
459 // case of failure. | |
460 return; | |
461 } | |
462 | |
463 // Send response back to ReadFile request. | |
464 pp::VarArrayBuffer array_buffer(read_bytes); | |
465 if (read_bytes > 0) { | |
466 char* array_buffer_data = static_cast<char*>(array_buffer.Map()); | |
467 memcpy(array_buffer_data, destination_buffer, read_bytes); | |
468 array_buffer.Unmap(); | |
469 } | |
470 | |
471 bool has_more_data = left_length - read_bytes > 0 && read_bytes > 0; | |
472 message_sender_->SendReadFileDone( | |
473 file_system_id_, request_id, array_buffer, has_more_data); | |
474 | |
475 if (read_bytes == 0) | |
476 break; // No more available data. | |
477 | |
478 left_length -= read_bytes; | |
479 offset += read_bytes; | |
480 } | |
481 volume_archive_->MaybeDecompressAhead(); | |
482 } | |
483 | |
484 | |
485 void Volume::ClearJob() { | |
486 job_lock_.Acquire(); | |
487 reader_request_id_ = ""; | |
488 job_lock_.Release(); | |
489 } | |
OLD | NEW |