Coverage Report

Created: 2018-09-25 14:53

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