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