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_reader_javascript_stream.h" | |
6 | |
7 #include <algorithm> | |
8 #include <limits> | |
9 | |
10 #include "archive.h" | |
11 #include "ppapi/cpp/logging.h" | |
12 | |
13 VolumeReaderJavaScriptStream::VolumeReaderJavaScriptStream( | |
14 int64_t archive_size, | |
15 JavaScriptRequestorInterface* requestor) | |
16 : archive_size_(archive_size), | |
17 requestor_(requestor), | |
18 available_data_(false), | |
19 read_error_(false), | |
20 passphrase_error_(false), | |
21 offset_(0), | |
22 last_read_chunk_offset_(-1) /* For first call -1 will force a chunk | |
23 request from JavaScript as offset | |
24 parameter is 0. */, | |
25 read_ahead_array_buffer_ptr_(&first_array_buffer_) { | |
26 pthread_mutex_init(&shared_state_lock_, NULL); | |
27 pthread_cond_init(&available_data_cond_, NULL); | |
28 pthread_cond_init(&available_passphrase_cond_, NULL); | |
29 | |
30 // Dummy Map the second buffer as first buffer is used for read ahead by | |
31 // read_ahead_array_buffer_ptr_. This operation is required in order for Unmap | |
32 // to correctly work in the destructor and VolumeReaderJavaScriptStream::Read. | |
33 second_array_buffer_.Map(); | |
34 } | |
35 | |
36 VolumeReaderJavaScriptStream::~VolumeReaderJavaScriptStream() { | |
37 pthread_mutex_destroy(&shared_state_lock_); | |
38 pthread_cond_destroy(&available_data_cond_); | |
39 pthread_cond_destroy(&available_passphrase_cond_); | |
40 | |
41 // Unmap last mapped buffer. This is the other buffer to | |
42 // read_ahead_array_buffer_ptr_ as read_ahead_array_buffer_ptr_ must be | |
43 // available for SetBufferAndSignal to overwrite. | |
44 if (read_ahead_array_buffer_ptr_ != &first_array_buffer_) | |
45 first_array_buffer_.Unmap(); | |
46 else | |
47 second_array_buffer_.Unmap(); | |
48 }; | |
49 | |
50 void VolumeReaderJavaScriptStream::SetBufferAndSignal( | |
51 const pp::VarArrayBuffer& array_buffer, | |
52 int64_t read_offset) { | |
53 PP_DCHECK(read_offset >= 0); | |
54 | |
55 // Ignore read ahead in case offset was changed using Skip or Seek and in case | |
56 // we already have available data. This can happen in case of 2+ RequestChunk | |
57 // calls done in parallel as a result of calling Read, Skip and Seek one after | |
58 // another really fast. The usage of the buffer is not guarded so in case we | |
59 // overwrite *read_ahead_array_buffer_ptr_ we will end up with memory | |
60 // corruption. | |
61 // In case read_offset and offset_ are different, then the read ahead data is | |
62 // not valid anymore, but in case they are equal and available_data_ is set to | |
63 // true then the second read ahead data is the same as the first read ahead | |
64 // data so we can just ignore it. | |
65 | |
66 // TODO(mtomasz): We don't need to discard everything. Sometimes part of the | |
67 // buffer can still be used. In such case we should use it. That can greatly | |
68 // improve traversing headers for archives with small files! | |
69 | |
70 pthread_mutex_lock(&shared_state_lock_); | |
71 if (read_offset == offset_ && !available_data_ && !read_error_) { | |
72 // Signal VolumeReaderJavaScriptStream::Read to continue execution. Copies | |
73 // buffer locally so libarchive has the buffer in memory when working with | |
74 // it. Though we acquire a lock here this call is blocking only for a few | |
75 // moments as VolumeReaderJavaScriptStream::Read will release the lock with | |
76 // pthread_cond_wait. So we cannot arrive at a deadlock that will block the | |
77 // main thread. | |
78 | |
79 *read_ahead_array_buffer_ptr_ = array_buffer; // Copy operation. | |
80 available_data_ = true; | |
81 | |
82 pthread_cond_signal(&available_data_cond_); | |
83 } | |
84 pthread_mutex_unlock(&shared_state_lock_); | |
85 } | |
86 | |
87 void VolumeReaderJavaScriptStream::ReadErrorSignal() { | |
88 pthread_mutex_lock(&shared_state_lock_); | |
89 read_error_ = true; // Read error from JavaScript. | |
90 pthread_cond_signal(&available_data_cond_); | |
91 pthread_mutex_unlock(&shared_state_lock_); | |
92 } | |
93 | |
94 void VolumeReaderJavaScriptStream::SetPassphraseAndSignal( | |
95 const std::string& passphrase) { | |
96 pthread_mutex_lock(&shared_state_lock_); | |
97 // Signal VolumeReaderJavaScriptStream::Passphrase to continue execution. | |
98 available_passphrase_ = passphrase; | |
99 pthread_cond_signal(&available_passphrase_cond_); | |
100 pthread_mutex_unlock(&shared_state_lock_); | |
101 } | |
102 | |
103 void VolumeReaderJavaScriptStream::PassphraseErrorSignal() { | |
104 pthread_mutex_lock(&shared_state_lock_); | |
105 passphrase_error_ = true; // Passphrase error from JavaScript. | |
106 pthread_cond_signal(&available_passphrase_cond_); | |
107 pthread_mutex_unlock(&shared_state_lock_); | |
108 } | |
109 | |
110 int64_t VolumeReaderJavaScriptStream::Read(int64_t bytes_to_read, | |
111 const void** destination_buffer) { | |
112 PP_DCHECK(bytes_to_read > 0); | |
113 | |
114 pthread_mutex_lock(&shared_state_lock_); | |
115 | |
116 // No more data, so signal end of reading. | |
117 if (offset_ >= archive_size_) { | |
118 pthread_mutex_unlock(&shared_state_lock_); | |
119 return 0; | |
120 } | |
121 | |
122 // Call in case of first read or read after Seek and Skip. | |
123 if (last_read_chunk_offset_ != offset_) | |
124 RequestChunk(bytes_to_read); | |
125 | |
126 if (!available_data_) { | |
127 // Wait for data from JavaScript. | |
128 while (!available_data_) { // Check again available data as first call | |
129 // was done outside guarded zone. | |
130 if (read_error_) { | |
131 pthread_mutex_unlock(&shared_state_lock_); | |
132 return ARCHIVE_FATAL; | |
133 } | |
134 pthread_cond_wait(&available_data_cond_, &shared_state_lock_); | |
135 } | |
136 } | |
137 | |
138 if (read_error_) { // Read ahead failed. | |
139 pthread_mutex_unlock(&shared_state_lock_); | |
140 return ARCHIVE_FATAL; | |
141 } | |
142 | |
143 // Make data available for libarchive custom read. No need to lock this part. | |
144 // The reason is that VolumeReaderJavaScriptStream::RequestChunk is the only | |
145 // function that can set available_data_ back to false and let | |
146 // VolumeReaderJavaScriptStream::SetBufferAndSignal overwrite the buffer. But | |
147 // reading ahead is done only at the end of this function after the buffers | |
148 // are switched. | |
149 *destination_buffer = read_ahead_array_buffer_ptr_->Map(); | |
150 int64_t bytes_read = | |
151 std::min(static_cast<int64_t>(read_ahead_array_buffer_ptr_->ByteLength()), | |
152 bytes_to_read); | |
153 | |
154 offset_ += bytes_read; | |
155 last_read_chunk_offset_ = offset_; | |
156 | |
157 // Ask for more data from JavaScript in the other buffer. This is the only | |
158 // time when we switch buffers. The reason is that libarchive must | |
159 // always work on valid data and that data must be available until next | |
160 // VolumeReaderJavaScriptStream::Read call, and as the data can be received | |
161 // at any time from JavaScript, we need a buffer to store it in case of | |
162 // reading ahead. | |
163 read_ahead_array_buffer_ptr_ = | |
164 read_ahead_array_buffer_ptr_ != &first_array_buffer_ | |
165 ? &first_array_buffer_ | |
166 : &second_array_buffer_; | |
167 | |
168 // Unmap old buffer. Only Read and constructor can Map the buffers so Read and | |
169 // destructor should be the one to Unmap them. This will work because it is | |
170 // called before RequestChunk which is the only method that overwrites the | |
171 // buffer. The constructor should also Map a default pp::VarArrayBuffer and | |
172 // destructor Unmap the last used array buffer (which is the other buffer than | |
173 // read_ahead_array_buffer_ptr_). Unfortunately it's not clear from the | |
174 // API description if this call is done automatically on pp::VarArrayBuffer | |
175 // destructor. | |
176 read_ahead_array_buffer_ptr_->Unmap(); | |
177 | |
178 // Read ahead next chunk with a length similar to current read. | |
179 RequestChunk(bytes_to_read); | |
180 pthread_mutex_unlock(&shared_state_lock_); | |
181 | |
182 return bytes_read; | |
183 } | |
184 | |
185 int64_t VolumeReaderJavaScriptStream::Seek(int64_t offset, int whence) { | |
186 pthread_mutex_lock(&shared_state_lock_); | |
187 | |
188 int64_t new_offset = offset_; | |
189 switch (whence) { | |
190 case SEEK_SET: | |
191 new_offset = offset; | |
192 break; | |
193 case SEEK_CUR: | |
194 new_offset += offset; | |
195 break; | |
196 case SEEK_END: | |
197 new_offset = archive_size_ + offset; | |
198 break; | |
199 default: | |
200 PP_NOTREACHED(); | |
201 pthread_mutex_unlock(&shared_state_lock_); | |
202 return ARCHIVE_FATAL; | |
203 } | |
204 | |
205 if (new_offset < 0 || new_offset > archive_size_) { | |
206 pthread_mutex_unlock(&shared_state_lock_); | |
207 return ARCHIVE_FATAL; | |
208 } | |
209 | |
210 offset_ = new_offset; | |
211 pthread_mutex_unlock(&shared_state_lock_); | |
212 | |
213 return new_offset; | |
214 } | |
215 | |
216 int64_t VolumeReaderJavaScriptStream::Skip(int64_t bytes_to_skip) { | |
217 pthread_mutex_lock(&shared_state_lock_); | |
218 // Invalid bytes_to_skip. This "if" can be triggered for corrupted archives. | |
219 // We return 0 instead of ARCHIVE_FATAL in order for libarchive to use normal | |
220 // Read and return the correct error. In case we return ARCHIVE_FATAL here | |
221 // then libarchive just stops without telling us why it wasn't able to | |
222 // process the archive. | |
223 if (archive_size_ - offset_ < bytes_to_skip || bytes_to_skip < 0) { | |
224 pthread_mutex_unlock(&shared_state_lock_); | |
225 return 0; | |
226 } | |
227 | |
228 offset_ += bytes_to_skip; | |
229 pthread_mutex_unlock(&shared_state_lock_); | |
230 | |
231 return bytes_to_skip; | |
232 } | |
233 | |
234 void VolumeReaderJavaScriptStream::SetRequestId(const std::string& request_id) { | |
235 // No lock necessary, as request_id is used by one thread only. | |
236 request_id_ = request_id; | |
237 } | |
238 | |
239 const char* VolumeReaderJavaScriptStream::Passphrase() { | |
240 // The error is not recoverable. Once passphrase fails to be provided, it is | |
241 // never asked again. Note, that still users are able to retry entering the | |
242 // password, unless they click Cancel. | |
243 pthread_mutex_lock(&shared_state_lock_); | |
244 if (passphrase_error_) { | |
245 pthread_mutex_unlock(&shared_state_lock_); | |
246 return NULL; | |
247 } | |
248 pthread_mutex_unlock(&shared_state_lock_); | |
249 | |
250 // Request the passphrase outside of the lock. | |
251 requestor_->RequestPassphrase(request_id_); | |
252 | |
253 pthread_mutex_lock(&shared_state_lock_); | |
254 // Wait for the passphrase from JavaScript. | |
255 pthread_cond_wait(&available_passphrase_cond_, &shared_state_lock_); | |
256 const char* result = NULL; | |
257 if (!passphrase_error_) | |
258 result = strdup(available_passphrase_.c_str()); | |
259 pthread_mutex_unlock(&shared_state_lock_); | |
260 | |
261 return result; | |
262 } | |
263 | |
264 void VolumeReaderJavaScriptStream::RequestChunk(int64_t length) { | |
265 // Read next chunk only if not at the end of archive. | |
266 if (archive_size_ <= offset_) | |
267 return; | |
268 | |
269 int64_t bytes_to_read = | |
270 std::min(length, archive_size_ - offset_ /* Positive check above. */); | |
271 available_data_ = false; | |
272 | |
273 requestor_->RequestFileChunk(request_id_, offset_, bytes_to_read); | |
274 } | |
OLD | NEW |