Line data Source code
1 : /* This Source Code Form is subject to the terms of the Mozilla Public
2 : * License, v. 2.0. If a copy of the MPL was not distributed with this
3 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 :
5 : #include "CacheLog.h"
6 : #include "CacheFileOutputStream.h"
7 :
8 : #include "CacheFile.h"
9 : #include "CacheEntry.h"
10 : #include "nsStreamUtils.h"
11 : #include "nsThreadUtils.h"
12 : #include "mozilla/DebugOnly.h"
13 : #include <algorithm>
14 :
15 : namespace mozilla {
16 : namespace net {
17 :
18 17 : NS_IMPL_ADDREF(CacheFileOutputStream)
19 : NS_IMETHODIMP_(MozExternalRefCountType)
20 17 : CacheFileOutputStream::Release()
21 : {
22 17 : NS_PRECONDITION(0 != mRefCnt, "dup release");
23 17 : nsrefcnt count = --mRefCnt;
24 17 : NS_LOG_RELEASE(this, count, "CacheFileOutputStream");
25 :
26 17 : if (0 == count) {
27 2 : mRefCnt = 1;
28 : {
29 4 : CacheFileAutoLock lock(mFile);
30 2 : mFile->RemoveOutput(this, mStatus);
31 : }
32 2 : delete (this);
33 2 : return 0;
34 : }
35 :
36 15 : return count;
37 : }
38 :
39 11 : NS_INTERFACE_MAP_BEGIN(CacheFileOutputStream)
40 11 : NS_INTERFACE_MAP_ENTRY(nsIOutputStream)
41 2 : NS_INTERFACE_MAP_ENTRY(nsIAsyncOutputStream)
42 2 : NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
43 0 : NS_INTERFACE_MAP_ENTRY(mozilla::net::CacheFileChunkListener)
44 0 : NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIOutputStream)
45 0 : NS_INTERFACE_MAP_END_THREADSAFE
46 :
47 2 : CacheFileOutputStream::CacheFileOutputStream(CacheFile *aFile,
48 : CacheOutputCloseListener *aCloseListener,
49 2 : bool aAlternativeData)
50 : : mFile(aFile)
51 : , mCloseListener(aCloseListener)
52 : , mPos(0)
53 : , mClosed(false)
54 : , mAlternativeData(aAlternativeData)
55 : , mStatus(NS_OK)
56 2 : , mCallbackFlags(0)
57 : {
58 2 : LOG(("CacheFileOutputStream::CacheFileOutputStream() [this=%p]", this));
59 :
60 2 : if (mAlternativeData) {
61 0 : mPos = mFile->mAltDataOffset;
62 : }
63 2 : }
64 :
65 6 : CacheFileOutputStream::~CacheFileOutputStream()
66 : {
67 2 : LOG(("CacheFileOutputStream::~CacheFileOutputStream() [this=%p]", this));
68 6 : }
69 :
70 : // nsIOutputStream
71 : NS_IMETHODIMP
72 0 : CacheFileOutputStream::Close()
73 : {
74 0 : LOG(("CacheFileOutputStream::Close() [this=%p]", this));
75 0 : return CloseWithStatus(NS_OK);
76 : }
77 :
78 : NS_IMETHODIMP
79 0 : CacheFileOutputStream::Flush()
80 : {
81 : // TODO do we need to implement flush ???
82 0 : LOG(("CacheFileOutputStream::Flush() [this=%p]", this));
83 0 : return NS_OK;
84 : }
85 :
86 : NS_IMETHODIMP
87 2 : CacheFileOutputStream::Write(const char * aBuf, uint32_t aCount,
88 : uint32_t *_retval)
89 : {
90 4 : CacheFileAutoLock lock(mFile);
91 :
92 2 : LOG(("CacheFileOutputStream::Write() [this=%p, count=%d]", this, aCount));
93 :
94 2 : if (mClosed) {
95 0 : LOG(("CacheFileOutputStream::Write() - Stream is closed. [this=%p, "
96 : "status=0x%08" PRIx32"]", this, static_cast<uint32_t>(mStatus)));
97 :
98 0 : return NS_FAILED(mStatus) ? mStatus : NS_BASE_STREAM_CLOSED;
99 : }
100 :
101 2 : if (!mFile->mSkipSizeCheck && CacheObserver::EntryIsTooBig(mPos + aCount, !mFile->mMemoryOnly)) {
102 0 : LOG(("CacheFileOutputStream::Write() - Entry is too big, failing and "
103 : "dooming the entry. [this=%p]", this));
104 :
105 0 : mFile->DoomLocked(nullptr);
106 0 : CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
107 0 : return NS_ERROR_FILE_TOO_BIG;
108 : }
109 :
110 : // We use 64-bit offset when accessing the file, unfortunately we use 32-bit
111 : // metadata offset, so we cannot handle data bigger than 4GB.
112 2 : if (mPos + aCount > PR_UINT32_MAX) {
113 0 : LOG(("CacheFileOutputStream::Write() - Entry's size exceeds 4GB while it "
114 : "isn't too big according to CacheObserver::EntryIsTooBig(). Failing "
115 : "and dooming the entry. [this=%p]", this));
116 :
117 0 : mFile->DoomLocked(nullptr);
118 0 : CloseWithStatusLocked(NS_ERROR_FILE_TOO_BIG);
119 0 : return NS_ERROR_FILE_TOO_BIG;
120 : }
121 :
122 2 : *_retval = aCount;
123 :
124 6 : while (aCount) {
125 2 : EnsureCorrectChunk(false);
126 2 : if (NS_FAILED(mStatus)) {
127 0 : return mStatus;
128 : }
129 :
130 2 : FillHole();
131 2 : if (NS_FAILED(mStatus)) {
132 0 : return mStatus;
133 : }
134 :
135 2 : uint32_t chunkOffset = mPos - (mPos / kChunkSize) * kChunkSize;
136 2 : uint32_t canWrite = kChunkSize - chunkOffset;
137 2 : uint32_t thisWrite = std::min(static_cast<uint32_t>(canWrite), aCount);
138 :
139 4 : CacheFileChunkWriteHandle hnd = mChunk->GetWriteHandle(chunkOffset + thisWrite);
140 2 : if (!hnd.Buf()) {
141 0 : CloseWithStatusLocked(NS_ERROR_OUT_OF_MEMORY);
142 0 : return NS_ERROR_OUT_OF_MEMORY;
143 : }
144 :
145 2 : memcpy(hnd.Buf() + chunkOffset, aBuf, thisWrite);
146 2 : hnd.UpdateDataSize(chunkOffset, thisWrite);
147 :
148 2 : mPos += thisWrite;
149 2 : aBuf += thisWrite;
150 2 : aCount -= thisWrite;
151 : }
152 :
153 2 : EnsureCorrectChunk(true);
154 :
155 2 : LOG(("CacheFileOutputStream::Write() - Wrote %d bytes [this=%p]",
156 : *_retval, this));
157 :
158 2 : return NS_OK;
159 : }
160 :
161 : NS_IMETHODIMP
162 0 : CacheFileOutputStream::WriteFrom(nsIInputStream *aFromStream, uint32_t aCount,
163 : uint32_t *_retval)
164 : {
165 0 : LOG(("CacheFileOutputStream::WriteFrom() - NOT_IMPLEMENTED [this=%p, from=%p"
166 : ", count=%d]", this, aFromStream, aCount));
167 :
168 0 : return NS_ERROR_NOT_IMPLEMENTED;
169 : }
170 :
171 : NS_IMETHODIMP
172 0 : CacheFileOutputStream::WriteSegments(nsReadSegmentFun aReader, void *aClosure,
173 : uint32_t aCount, uint32_t *_retval)
174 : {
175 0 : LOG(("CacheFileOutputStream::WriteSegments() - NOT_IMPLEMENTED [this=%p, "
176 : "count=%d]", this, aCount));
177 :
178 0 : return NS_ERROR_NOT_IMPLEMENTED;
179 : }
180 :
181 : NS_IMETHODIMP
182 2 : CacheFileOutputStream::IsNonBlocking(bool *_retval)
183 : {
184 2 : *_retval = false;
185 2 : return NS_OK;
186 : }
187 :
188 : // nsIAsyncOutputStream
189 : NS_IMETHODIMP
190 0 : CacheFileOutputStream::CloseWithStatus(nsresult aStatus)
191 : {
192 0 : CacheFileAutoLock lock(mFile);
193 :
194 0 : LOG(("CacheFileOutputStream::CloseWithStatus() [this=%p, aStatus=0x%08" PRIx32 "]",
195 : this, static_cast<uint32_t>(aStatus)));
196 :
197 0 : return CloseWithStatusLocked(aStatus);
198 : }
199 :
200 : nsresult
201 0 : CacheFileOutputStream::CloseWithStatusLocked(nsresult aStatus)
202 : {
203 0 : LOG(("CacheFileOutputStream::CloseWithStatusLocked() [this=%p, "
204 : "aStatus=0x%08" PRIx32 "]", this, static_cast<uint32_t>(aStatus)));
205 :
206 0 : if (mClosed) {
207 0 : MOZ_ASSERT(!mCallback);
208 0 : return NS_OK;
209 : }
210 :
211 0 : mClosed = true;
212 0 : mStatus = NS_FAILED(aStatus) ? aStatus : NS_BASE_STREAM_CLOSED;
213 :
214 0 : if (mChunk) {
215 0 : ReleaseChunk();
216 : }
217 :
218 0 : if (mCallback) {
219 0 : NotifyListener();
220 : }
221 :
222 0 : mFile->RemoveOutput(this, mStatus);
223 :
224 0 : return NS_OK;
225 : }
226 :
227 : NS_IMETHODIMP
228 0 : CacheFileOutputStream::AsyncWait(nsIOutputStreamCallback *aCallback,
229 : uint32_t aFlags,
230 : uint32_t aRequestedCount,
231 : nsIEventTarget *aEventTarget)
232 : {
233 0 : CacheFileAutoLock lock(mFile);
234 :
235 0 : LOG(("CacheFileOutputStream::AsyncWait() [this=%p, callback=%p, flags=%d, "
236 : "requestedCount=%d, eventTarget=%p]", this, aCallback, aFlags,
237 : aRequestedCount, aEventTarget));
238 :
239 0 : mCallback = aCallback;
240 0 : mCallbackFlags = aFlags;
241 0 : mCallbackTarget = aEventTarget;
242 :
243 0 : if (!mCallback)
244 0 : return NS_OK;
245 :
246 : // The stream is blocking so it is writable at any time
247 0 : if (mClosed || !(aFlags & WAIT_CLOSURE_ONLY))
248 0 : NotifyListener();
249 :
250 0 : return NS_OK;
251 : }
252 :
253 : // nsISeekableStream
254 : NS_IMETHODIMP
255 2 : CacheFileOutputStream::Seek(int32_t whence, int64_t offset)
256 : {
257 4 : CacheFileAutoLock lock(mFile);
258 :
259 2 : LOG(("CacheFileOutputStream::Seek() [this=%p, whence=%d, offset=%" PRId64 "]",
260 : this, whence, offset));
261 :
262 2 : if (mClosed) {
263 0 : LOG(("CacheFileOutputStream::Seek() - Stream is closed. [this=%p]", this));
264 0 : return NS_BASE_STREAM_CLOSED;
265 : }
266 :
267 2 : int64_t newPos = offset;
268 2 : switch (whence) {
269 : case NS_SEEK_SET:
270 2 : if (mAlternativeData) {
271 0 : newPos += mFile->mAltDataOffset;
272 : }
273 2 : break;
274 : case NS_SEEK_CUR:
275 0 : newPos += mPos;
276 0 : break;
277 : case NS_SEEK_END:
278 0 : if (mAlternativeData) {
279 0 : newPos += mFile->mDataSize;
280 : } else {
281 0 : newPos += mFile->mAltDataOffset;
282 : }
283 0 : break;
284 : default:
285 0 : NS_ERROR("invalid whence");
286 0 : return NS_ERROR_INVALID_ARG;
287 : }
288 2 : mPos = newPos;
289 2 : EnsureCorrectChunk(true);
290 :
291 2 : LOG(("CacheFileOutputStream::Seek() [this=%p, pos=%" PRId64 "]", this, mPos));
292 2 : return NS_OK;
293 : }
294 :
295 : NS_IMETHODIMP
296 0 : CacheFileOutputStream::Tell(int64_t *_retval)
297 : {
298 0 : CacheFileAutoLock lock(mFile);
299 :
300 0 : if (mClosed) {
301 0 : LOG(("CacheFileOutputStream::Tell() - Stream is closed. [this=%p]", this));
302 0 : return NS_BASE_STREAM_CLOSED;
303 : }
304 :
305 0 : *_retval = mPos;
306 :
307 0 : if (mAlternativeData) {
308 0 : *_retval -= mFile->mAltDataOffset;
309 : }
310 :
311 0 : LOG(("CacheFileOutputStream::Tell() [this=%p, retval=%" PRId64 "]", this, *_retval));
312 0 : return NS_OK;
313 : }
314 :
315 : NS_IMETHODIMP
316 0 : CacheFileOutputStream::SetEOF()
317 : {
318 0 : MOZ_ASSERT(false, "CacheFileOutputStream::SetEOF() not implemented");
319 : // Right now we don't use SetEOF(). If we ever need this method, we need
320 : // to think about what to do with input streams that already points beyond
321 : // new EOF.
322 : return NS_ERROR_NOT_IMPLEMENTED;
323 : }
324 :
325 : // CacheFileChunkListener
326 : nsresult
327 0 : CacheFileOutputStream::OnChunkRead(nsresult aResult, CacheFileChunk *aChunk)
328 : {
329 0 : MOZ_CRASH("CacheFileOutputStream::OnChunkRead should not be called!");
330 : return NS_ERROR_UNEXPECTED;
331 : }
332 :
333 : nsresult
334 0 : CacheFileOutputStream::OnChunkWritten(nsresult aResult, CacheFileChunk *aChunk)
335 : {
336 0 : MOZ_CRASH(
337 : "CacheFileOutputStream::OnChunkWritten should not be called!");
338 : return NS_ERROR_UNEXPECTED;
339 : }
340 :
341 : nsresult
342 0 : CacheFileOutputStream::OnChunkAvailable(nsresult aResult,
343 : uint32_t aChunkIdx,
344 : CacheFileChunk *aChunk)
345 : {
346 0 : MOZ_CRASH(
347 : "CacheFileOutputStream::OnChunkAvailable should not be called!");
348 : return NS_ERROR_UNEXPECTED;
349 : }
350 :
351 : nsresult
352 0 : CacheFileOutputStream::OnChunkUpdated(CacheFileChunk *aChunk)
353 : {
354 0 : MOZ_CRASH(
355 : "CacheFileOutputStream::OnChunkUpdated should not be called!");
356 : return NS_ERROR_UNEXPECTED;
357 : }
358 :
359 2 : void CacheFileOutputStream::NotifyCloseListener()
360 : {
361 4 : RefPtr<CacheOutputCloseListener> listener;
362 2 : listener.swap(mCloseListener);
363 2 : if (!listener)
364 0 : return;
365 :
366 2 : listener->OnOutputClosed();
367 : }
368 :
369 : void
370 0 : CacheFileOutputStream::ReleaseChunk()
371 : {
372 0 : LOG(("CacheFileOutputStream::ReleaseChunk() [this=%p, idx=%d]",
373 : this, mChunk->Index()));
374 :
375 0 : mFile->ReleaseOutsideLock(mChunk.forget());
376 0 : }
377 :
378 : void
379 6 : CacheFileOutputStream::EnsureCorrectChunk(bool aReleaseOnly)
380 : {
381 6 : mFile->AssertOwnsLock();
382 :
383 6 : LOG(("CacheFileOutputStream::EnsureCorrectChunk() [this=%p, releaseOnly=%d]",
384 : this, aReleaseOnly));
385 :
386 6 : uint32_t chunkIdx = mPos / kChunkSize;
387 :
388 6 : if (mChunk) {
389 2 : if (mChunk->Index() == chunkIdx) {
390 : // we have a correct chunk
391 2 : LOG(("CacheFileOutputStream::EnsureCorrectChunk() - Have correct chunk "
392 : "[this=%p, idx=%d]", this, chunkIdx));
393 :
394 2 : return;
395 : }
396 : else {
397 0 : ReleaseChunk();
398 : }
399 : }
400 :
401 4 : if (aReleaseOnly)
402 2 : return;
403 :
404 : nsresult rv;
405 2 : rv = mFile->GetChunkLocked(chunkIdx, CacheFile::WRITER, nullptr,
406 4 : getter_AddRefs(mChunk));
407 2 : if (NS_FAILED(rv)) {
408 0 : LOG(("CacheFileOutputStream::EnsureCorrectChunk() - GetChunkLocked failed. "
409 : "[this=%p, idx=%d, rv=0x%08" PRIx32 "]", this, chunkIdx,
410 : static_cast<uint32_t>(rv)));
411 0 : CloseWithStatusLocked(rv);
412 : }
413 : }
414 :
415 : void
416 2 : CacheFileOutputStream::FillHole()
417 : {
418 2 : mFile->AssertOwnsLock();
419 :
420 2 : MOZ_ASSERT(mChunk);
421 2 : MOZ_ASSERT(mPos / kChunkSize == mChunk->Index());
422 :
423 2 : uint32_t pos = mPos - (mPos / kChunkSize) * kChunkSize;
424 2 : if (mChunk->DataSize() >= pos)
425 4 : return;
426 :
427 0 : LOG(("CacheFileOutputStream::FillHole() - Zeroing hole in chunk %d, range "
428 : "%d-%d [this=%p]", mChunk->Index(), mChunk->DataSize(), pos - 1, this));
429 :
430 0 : CacheFileChunkWriteHandle hnd = mChunk->GetWriteHandle(pos);
431 0 : if (!hnd.Buf()) {
432 0 : CloseWithStatusLocked(NS_ERROR_OUT_OF_MEMORY);
433 0 : return;
434 : }
435 :
436 0 : uint32_t offset = hnd.DataSize();
437 0 : memset(hnd.Buf() + offset, 0, pos - offset);
438 0 : hnd.UpdateDataSize(offset, pos - offset);
439 : }
440 :
441 : void
442 0 : CacheFileOutputStream::NotifyListener()
443 : {
444 0 : mFile->AssertOwnsLock();
445 :
446 0 : LOG(("CacheFileOutputStream::NotifyListener() [this=%p]", this));
447 :
448 0 : MOZ_ASSERT(mCallback);
449 :
450 0 : if (!mCallbackTarget) {
451 0 : mCallbackTarget = CacheFileIOManager::IOTarget();
452 0 : if (!mCallbackTarget) {
453 0 : LOG(("CacheFileOutputStream::NotifyListener() - Cannot get Cache I/O "
454 : "thread! Using main thread for callback."));
455 0 : mCallbackTarget = GetMainThreadEventTarget();
456 : }
457 : }
458 :
459 : nsCOMPtr<nsIOutputStreamCallback> asyncCallback =
460 0 : NS_NewOutputStreamReadyEvent(mCallback, mCallbackTarget);
461 :
462 0 : mCallback = nullptr;
463 0 : mCallbackTarget = nullptr;
464 :
465 0 : asyncCallback->OnOutputStreamReady(this);
466 0 : }
467 :
468 : // Memory reporting
469 :
470 : size_t
471 0 : CacheFileOutputStream::SizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const
472 : {
473 : // Everything the stream keeps a reference to is already reported somewhere else.
474 : // mFile reports itself.
475 : // mChunk reported as part of CacheFile.
476 : // mCloseListener is CacheEntry, already reported.
477 : // mCallback is usually CacheFile or a class that is reported elsewhere.
478 0 : return mallocSizeOf(this);
479 : }
480 :
481 : } // namespace net
482 : } // namespace mozilla
|