| // 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); |
| } |