| 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 |