/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsTemporaryFileInputStream.h" #include "nsStreamUtils.h" #include <algorithm> typedef mozilla::ipc::FileDescriptor::PlatformHandleType FileHandleType; NS_IMPL_ISUPPORTS(nsTemporaryFileInputStream, nsIInputStream, nsISeekableStream, nsIIPCSerializableInputStream) nsTemporaryFileInputStream::nsTemporaryFileInputStream(FileDescOwner* aFileDescOwner, uint64_t aStartPos, uint64_t aEndPos) : mFileDescOwner(aFileDescOwner), mStartPos(aStartPos), mCurPos(aStartPos), mEndPos(aEndPos), mClosed(false) { NS_ASSERTION(aStartPos <= aEndPos, "StartPos should less equal than EndPos!"); } nsTemporaryFileInputStream::nsTemporaryFileInputStream() : mStartPos(0), mCurPos(0), mEndPos(0), mClosed(false) { } NS_IMETHODIMP nsTemporaryFileInputStream::Close() { mClosed = true; return NS_OK; } NS_IMETHODIMP nsTemporaryFileInputStream::Available(uint64_t * bytesAvailable) { if (mClosed) return NS_BASE_STREAM_CLOSED; NS_ASSERTION(mCurPos <= mEndPos, "CurPos should less equal than EndPos!"); *bytesAvailable = mEndPos - mCurPos; return NS_OK; } NS_IMETHODIMP nsTemporaryFileInputStream::Read(char* buffer, uint32_t count, uint32_t* bytesRead) { return ReadSegments(NS_CopySegmentToBuffer, buffer, count, bytesRead); } NS_IMETHODIMP nsTemporaryFileInputStream::ReadSegments(nsWriteSegmentFun writer, void * closure, uint32_t count, uint32_t * result) { NS_ASSERTION(result, "null ptr"); NS_ASSERTION(mCurPos <= mEndPos, "bad stream state"); *result = 0; if (mClosed) { return NS_BASE_STREAM_CLOSED; } mozilla::MutexAutoLock lock(mFileDescOwner->FileMutex()); int64_t offset = PR_Seek64(mFileDescOwner->mFD, mCurPos, PR_SEEK_SET); if (offset == -1) { return NS_ErrorAccordingToNSPR(); } // Limit requested count to the amount remaining in our section of the file. count = std::min(count, uint32_t(mEndPos - mCurPos)); char buf[4096]; while (*result < count) { uint32_t bufCount = std::min(count - *result, (uint32_t) sizeof(buf)); int32_t bytesRead = PR_Read(mFileDescOwner->mFD, buf, bufCount); if (bytesRead == 0) { mClosed = true; return NS_OK; } if (bytesRead < 0) { return NS_ErrorAccordingToNSPR(); } int32_t bytesWritten = 0; while (bytesWritten < bytesRead) { uint32_t writerCount = 0; nsresult rv = writer(this, closure, buf + bytesWritten, *result, bytesRead - bytesWritten, &writerCount); if (NS_FAILED(rv) || writerCount == 0) { // nsIInputStream::ReadSegments' contract specifies that errors // from writer are not propagated to ReadSegments' caller. // // If writer fails, leaving bytes still in buf, that's okay: we // only update mCurPos to reflect successful writes, so the call // to PR_Seek64 at the top will restart us at the right spot. return NS_OK; } NS_ASSERTION(writerCount <= (uint32_t) (bytesRead - bytesWritten), "writer should not write more than we asked it to write"); bytesWritten += writerCount; *result += writerCount; mCurPos += writerCount; } } return NS_OK; } NS_IMETHODIMP nsTemporaryFileInputStream::IsNonBlocking(bool * nonBlocking) { *nonBlocking = false; return NS_OK; } NS_IMETHODIMP nsTemporaryFileInputStream::Seek(int32_t aWhence, int64_t aOffset) { if (mClosed) { return NS_BASE_STREAM_CLOSED; } switch (aWhence) { case nsISeekableStream::NS_SEEK_SET: aOffset += mStartPos; break; case nsISeekableStream::NS_SEEK_CUR: aOffset += mCurPos; break; case nsISeekableStream::NS_SEEK_END: aOffset += mEndPos; break; default: return NS_ERROR_FAILURE; } if (aOffset < (int64_t)mStartPos || aOffset > (int64_t)mEndPos) { return NS_ERROR_INVALID_ARG; } mCurPos = aOffset; return NS_OK; } NS_IMETHODIMP nsTemporaryFileInputStream::Tell(int64_t* aPos) { if (!aPos) { return NS_ERROR_FAILURE; } if (mClosed) { return NS_BASE_STREAM_CLOSED; } MOZ_ASSERT(mStartPos <= mCurPos, "StartPos should less equal than CurPos!"); *aPos = mCurPos - mStartPos; return NS_OK; } NS_IMETHODIMP nsTemporaryFileInputStream::SetEOF() { if (mClosed) { return NS_BASE_STREAM_CLOSED; } return Close(); } void nsTemporaryFileInputStream::Serialize(InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors) { TemporaryFileInputStreamParams params; MutexAutoLock lock(mFileDescOwner->FileMutex()); MOZ_ASSERT(mFileDescOwner->mFD); if (!mClosed) { FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFileDescOwner->mFD)); NS_ASSERTION(fd, "This should never be null!"); DebugOnly<FileDescriptor*> dbgFD = aFileDescriptors.AppendElement(fd); NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!"); params.fileDescriptorIndex() = aFileDescriptors.Length() - 1; Close(); } else { NS_WARNING("The stream is already closed. " "Sending an invalid file descriptor to the other process!"); params.fileDescriptorIndex() = UINT32_MAX; } params.startPos() = mCurPos; params.endPos() = mEndPos; aParams = params; } bool nsTemporaryFileInputStream::Deserialize(const InputStreamParams& aParams, const FileDescriptorArray& aFileDescriptors) { const TemporaryFileInputStreamParams& params = aParams.get_TemporaryFileInputStreamParams(); uint32_t fileDescriptorIndex = params.fileDescriptorIndex(); FileDescriptor fd; if (fileDescriptorIndex < aFileDescriptors.Length()) { fd = aFileDescriptors[fileDescriptorIndex]; NS_WARNING_ASSERTION(fd.IsValid(), "Received an invalid file descriptor!"); } else { NS_WARNING("Received a bad file descriptor index!"); } if (fd.IsValid()) { auto rawFD = fd.ClonePlatformHandle(); PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release())); if (!fileDesc) { NS_WARNING("Failed to import file handle!"); return false; } mFileDescOwner = new FileDescOwner(fileDesc); } else { mClosed = true; } mStartPos = mCurPos = params.startPos(); mEndPos = params.endPos(); return true; } Maybe<uint64_t> nsTemporaryFileInputStream::ExpectedSerializedLength() { return Nothing(); }