1 : /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 : *
3 : * ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is nsDiskCacheStreams.cpp, released
17 : * June 13, 2001.
18 : *
19 : * The Initial Developer of the Original Code is
20 : * Netscape Communications Corporation.
21 : * Portions created by the Initial Developer are Copyright (C) 2001
22 : * the Initial Developer. All Rights Reserved.
23 : *
24 : * Contributor(s):
25 : * Gordon Sheridan <gordon@netscape.com>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 :
42 : #include "nsDiskCache.h"
43 : #include "nsDiskCacheDevice.h"
44 : #include "nsDiskCacheStreams.h"
45 : #include "nsCacheService.h"
46 : #include "mozilla/FileUtils.h"
47 : #include "nsIDiskCacheStreamInternal.h"
48 : #include "nsThreadUtils.h"
49 : #include "mozilla/Telemetry.h"
50 : #include "mozilla/TimeStamp.h"
51 :
52 :
53 :
54 : // Assumptions:
55 : // - cache descriptors live for life of streams
56 : // - streams will only be used by FileTransport,
57 : // they will not be directly accessible to clients
58 : // - overlapped I/O is NOT supported
59 :
60 :
61 : /******************************************************************************
62 : * nsDiskCacheInputStream
63 : *****************************************************************************/
64 : class nsDiskCacheInputStream : public nsIInputStream {
65 :
66 : public:
67 :
68 : nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
69 : PRFileDesc * fileDesc,
70 : const char * buffer,
71 : PRUint32 endOfStream);
72 :
73 : virtual ~nsDiskCacheInputStream();
74 :
75 : NS_DECL_ISUPPORTS
76 : NS_DECL_NSIINPUTSTREAM
77 :
78 : private:
79 : nsDiskCacheStreamIO * mStreamIO; // backpointer to parent
80 : PRFileDesc * mFD;
81 : const char * mBuffer;
82 : PRUint32 mStreamEnd;
83 : PRUint32 mPos; // stream position
84 : bool mClosed;
85 : };
86 :
87 :
88 860 : NS_IMPL_THREADSAFE_ISUPPORTS1(nsDiskCacheInputStream, nsIInputStream)
89 :
90 :
91 172 : nsDiskCacheInputStream::nsDiskCacheInputStream( nsDiskCacheStreamIO * parent,
92 : PRFileDesc * fileDesc,
93 : const char * buffer,
94 : PRUint32 endOfStream)
95 : : mStreamIO(parent)
96 : , mFD(fileDesc)
97 : , mBuffer(buffer)
98 : , mStreamEnd(endOfStream)
99 : , mPos(0)
100 172 : , mClosed(false)
101 : {
102 172 : NS_ADDREF(mStreamIO);
103 172 : mStreamIO->IncrementInputStreamCount();
104 172 : }
105 :
106 :
107 344 : nsDiskCacheInputStream::~nsDiskCacheInputStream()
108 : {
109 172 : Close();
110 172 : mStreamIO->DecrementInputStreamCount();
111 172 : NS_RELEASE(mStreamIO);
112 688 : }
113 :
114 :
115 : NS_IMETHODIMP
116 344 : nsDiskCacheInputStream::Close()
117 : {
118 344 : if (!mClosed) {
119 172 : if (mFD) {
120 1 : (void) PR_Close(mFD);
121 1 : mFD = nsnull;
122 : }
123 172 : mClosed = true;
124 : }
125 344 : return NS_OK;
126 : }
127 :
128 :
129 : NS_IMETHODIMP
130 10 : nsDiskCacheInputStream::Available(PRUint32 * bytesAvailable)
131 : {
132 10 : if (mClosed) return NS_BASE_STREAM_CLOSED;
133 10 : if (mStreamEnd < mPos) return NS_ERROR_UNEXPECTED;
134 :
135 10 : *bytesAvailable = mStreamEnd - mPos;
136 10 : return NS_OK;
137 : }
138 :
139 :
140 : NS_IMETHODIMP
141 339 : nsDiskCacheInputStream::Read(char * buffer, PRUint32 count, PRUint32 * bytesRead)
142 : {
143 339 : *bytesRead = 0;
144 :
145 339 : if (mClosed)
146 0 : return NS_OK;
147 :
148 339 : if (mPos == mStreamEnd) return NS_OK;
149 172 : if (mPos > mStreamEnd) return NS_ERROR_UNEXPECTED;
150 :
151 172 : if (count > mStreamEnd - mPos)
152 167 : count = mStreamEnd - mPos;
153 :
154 172 : if (mFD) {
155 : // just read from file
156 1 : PRInt32 result = PR_Read(mFD, buffer, count);
157 1 : if (result < 0) return NS_ErrorAccordingToNSPR();
158 :
159 1 : mPos += (PRUint32)result;
160 1 : *bytesRead = (PRUint32)result;
161 :
162 171 : } else if (mBuffer) {
163 : // read data from mBuffer
164 171 : memcpy(buffer, mBuffer + mPos, count);
165 171 : mPos += count;
166 171 : *bytesRead = count;
167 : } else {
168 : // no data source for input stream
169 : }
170 :
171 172 : return NS_OK;
172 : }
173 :
174 :
175 : NS_IMETHODIMP
176 0 : nsDiskCacheInputStream::ReadSegments(nsWriteSegmentFun writer,
177 : void * closure,
178 : PRUint32 count,
179 : PRUint32 * bytesRead)
180 : {
181 0 : return NS_ERROR_NOT_IMPLEMENTED;
182 : }
183 :
184 :
185 : NS_IMETHODIMP
186 0 : nsDiskCacheInputStream::IsNonBlocking(bool * nonBlocking)
187 : {
188 0 : *nonBlocking = false;
189 0 : return NS_OK;
190 : }
191 :
192 :
193 : /******************************************************************************
194 : * nsDiskCacheOutputStream
195 : *****************************************************************************/
196 : class nsDiskCacheOutputStream : public nsIOutputStream
197 : , public nsIDiskCacheStreamInternal
198 : {
199 : public:
200 : nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent);
201 : virtual ~nsDiskCacheOutputStream();
202 :
203 : NS_DECL_ISUPPORTS
204 : NS_DECL_NSIOUTPUTSTREAM
205 : NS_DECL_NSIDISKCACHESTREAMINTERNAL
206 :
207 557 : void ReleaseStreamIO() { NS_IF_RELEASE(mStreamIO); }
208 :
209 : private:
210 : nsDiskCacheStreamIO * mStreamIO; // backpointer to parent
211 : bool mClosed;
212 : };
213 :
214 :
215 3937 : NS_IMPL_THREADSAFE_ISUPPORTS2(nsDiskCacheOutputStream,
216 : nsIOutputStream,
217 : nsIDiskCacheStreamInternal)
218 :
219 557 : nsDiskCacheOutputStream::nsDiskCacheOutputStream( nsDiskCacheStreamIO * parent)
220 : : mStreamIO(parent)
221 557 : , mClosed(false)
222 : {
223 557 : NS_ADDREF(mStreamIO);
224 557 : }
225 :
226 :
227 1114 : nsDiskCacheOutputStream::~nsDiskCacheOutputStream()
228 : {
229 557 : Close();
230 557 : ReleaseStreamIO();
231 2228 : }
232 :
233 :
234 : NS_IMETHODIMP
235 1124 : nsDiskCacheOutputStream::Close()
236 : {
237 1124 : nsresult rv = NS_OK;
238 1124 : mozilla::TimeStamp start = mozilla::TimeStamp::Now();
239 :
240 1124 : if (!mClosed) {
241 555 : mClosed = true;
242 : // tell parent streamIO we are closing
243 555 : rv = mStreamIO->CloseOutputStream(this);
244 : }
245 :
246 : mozilla::Telemetry::ID id;
247 1124 : if (NS_IsMainThread())
248 38 : id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_MAIN_THREAD;
249 : else
250 1086 : id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE;
251 :
252 1124 : mozilla::Telemetry::AccumulateTimeDelta(id, start);
253 :
254 1124 : return rv;
255 : }
256 :
257 : NS_IMETHODIMP
258 14 : nsDiskCacheOutputStream::CloseInternal()
259 : {
260 14 : nsresult rv = NS_OK;
261 14 : mozilla::TimeStamp start = mozilla::TimeStamp::Now();
262 :
263 14 : if (!mClosed) {
264 2 : mClosed = true;
265 : // tell parent streamIO we are closing
266 2 : rv = mStreamIO->CloseOutputStreamInternal(this);
267 : }
268 :
269 : mozilla::Telemetry::ID id;
270 14 : if (NS_IsMainThread())
271 14 : id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL_MAIN_THREAD;
272 : else
273 0 : id = mozilla::Telemetry::NETWORK_DISK_CACHE_OUTPUT_STREAM_CLOSE_INTERNAL;
274 :
275 14 : mozilla::Telemetry::AccumulateTimeDelta(id, start);
276 :
277 14 : return rv;
278 : }
279 :
280 : NS_IMETHODIMP
281 0 : nsDiskCacheOutputStream::Flush()
282 : {
283 0 : if (mClosed) return NS_BASE_STREAM_CLOSED;
284 : // yeah, yeah, well get to it...eventually...
285 0 : return NS_OK;
286 : }
287 :
288 :
289 : NS_IMETHODIMP
290 758 : nsDiskCacheOutputStream::Write(const char *buf, PRUint32 count, PRUint32 *bytesWritten)
291 : {
292 758 : if (mClosed) return NS_BASE_STREAM_CLOSED;
293 758 : return mStreamIO->Write(buf, count, bytesWritten);
294 : }
295 :
296 :
297 : NS_IMETHODIMP
298 0 : nsDiskCacheOutputStream::WriteFrom(nsIInputStream *inStream, PRUint32 count, PRUint32 *bytesWritten)
299 : {
300 0 : NS_NOTREACHED("WriteFrom");
301 0 : return NS_ERROR_NOT_IMPLEMENTED;
302 : }
303 :
304 :
305 : NS_IMETHODIMP
306 0 : nsDiskCacheOutputStream::WriteSegments( nsReadSegmentFun reader,
307 : void * closure,
308 : PRUint32 count,
309 : PRUint32 * bytesWritten)
310 : {
311 0 : NS_NOTREACHED("WriteSegments");
312 0 : return NS_ERROR_NOT_IMPLEMENTED;
313 : }
314 :
315 :
316 : NS_IMETHODIMP
317 0 : nsDiskCacheOutputStream::IsNonBlocking(bool * nonBlocking)
318 : {
319 0 : *nonBlocking = false;
320 0 : return NS_OK;
321 : }
322 :
323 :
324 :
325 : /******************************************************************************
326 : * nsDiskCacheStreamIO
327 : *****************************************************************************/
328 2878 : NS_IMPL_THREADSAFE_ISUPPORTS0(nsDiskCacheStreamIO)
329 :
330 : // we pick 16k as the max buffer size because that is the threshold above which
331 : // we are unable to store the data in the cache block files
332 : // see nsDiskCacheMap.[cpp,h]
333 : #define kMaxBufferSize (16 * 1024)
334 :
335 710 : nsDiskCacheStreamIO::nsDiskCacheStreamIO(nsDiskCacheBinding * binding)
336 : : mBinding(binding)
337 : , mOutStream(nsnull)
338 : , mInStreamCount(0)
339 : , mFD(nsnull)
340 : , mStreamPos(0)
341 : , mStreamEnd(0)
342 : , mBufPos(0)
343 : , mBufEnd(0)
344 : , mBufSize(0)
345 : , mBufDirty(false)
346 710 : , mBuffer(nsnull)
347 : {
348 710 : mDevice = (nsDiskCacheDevice *)mBinding->mCacheEntry->CacheDevice();
349 :
350 : // acquire "death grip" on cache service
351 710 : nsCacheService *service = nsCacheService::GlobalInstance();
352 710 : NS_ADDREF(service);
353 710 : }
354 :
355 :
356 2130 : nsDiskCacheStreamIO::~nsDiskCacheStreamIO()
357 : {
358 710 : Close();
359 :
360 : // release "death grip" on cache service
361 710 : nsCacheService *service = nsCacheService::GlobalInstance();
362 710 : NS_RELEASE(service);
363 2840 : }
364 :
365 :
366 : void
367 710 : nsDiskCacheStreamIO::Close()
368 : {
369 : // this should only be called from our destructor
370 : // no one is interested in us anymore, so we don't need to grab any locks
371 :
372 : // assert streams closed
373 710 : NS_ASSERTION(!mOutStream, "output stream still open");
374 710 : NS_ASSERTION(mInStreamCount == 0, "input stream still open");
375 710 : NS_ASSERTION(!mFD, "file descriptor not closed");
376 :
377 710 : DeleteBuffer();
378 710 : }
379 :
380 :
381 : // NOTE: called with service lock held
382 : nsresult
383 172 : nsDiskCacheStreamIO::GetInputStream(PRUint32 offset, nsIInputStream ** inputStream)
384 : {
385 172 : NS_ENSURE_ARG_POINTER(inputStream);
386 172 : NS_ENSURE_TRUE(offset == 0, NS_ERROR_NOT_IMPLEMENTED);
387 :
388 172 : *inputStream = nsnull;
389 :
390 172 : if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
391 :
392 172 : if (mOutStream) {
393 0 : NS_WARNING("already have an output stream open");
394 0 : return NS_ERROR_NOT_AVAILABLE;
395 : }
396 :
397 : nsresult rv;
398 172 : PRFileDesc * fd = nsnull;
399 :
400 172 : mStreamEnd = mBinding->mCacheEntry->DataSize();
401 172 : if (mStreamEnd == 0) {
402 : // there's no data to read
403 0 : NS_ASSERTION(!mBinding->mRecord.DataLocationInitialized(), "storage allocated for zero data size");
404 172 : } else if (mBinding->mRecord.DataFile() == 0) {
405 : // open file desc for data
406 1 : rv = OpenCacheFile(PR_RDONLY, &fd);
407 1 : if (NS_FAILED(rv)) return rv; // unable to open file
408 1 : NS_ASSERTION(fd, "cache stream lacking open file.");
409 :
410 171 : } else if (!mBuffer) {
411 : // read block file for data
412 158 : rv = ReadCacheBlocks();
413 158 : if (NS_FAILED(rv)) return rv;
414 : }
415 : // else, mBuffer already contains all of the data (left over from a
416 : // previous block-file read or write).
417 :
418 172 : NS_ASSERTION(!(fd && mBuffer), "ambiguous data sources for input stream");
419 :
420 : // create a new input stream
421 172 : nsDiskCacheInputStream * inStream = new nsDiskCacheInputStream(this, fd, mBuffer, mStreamEnd);
422 172 : if (!inStream) return NS_ERROR_OUT_OF_MEMORY;
423 :
424 172 : NS_ADDREF(*inputStream = inStream);
425 172 : return NS_OK;
426 : }
427 :
428 :
429 : // NOTE: called with service lock held
430 : nsresult
431 557 : nsDiskCacheStreamIO::GetOutputStream(PRUint32 offset, nsIOutputStream ** outputStream)
432 : {
433 557 : NS_ENSURE_ARG_POINTER(outputStream);
434 557 : *outputStream = nsnull;
435 :
436 557 : if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
437 :
438 557 : NS_ASSERTION(!mOutStream, "already have an output stream open");
439 557 : NS_ASSERTION(mInStreamCount == 0, "we already have input streams open");
440 557 : if (mOutStream || mInStreamCount) return NS_ERROR_NOT_AVAILABLE;
441 :
442 : // mBuffer lazily allocated, but might exist if a previous stream already
443 : // created one.
444 557 : mBufPos = 0;
445 557 : mStreamPos = 0;
446 557 : mStreamEnd = mBinding->mCacheEntry->DataSize();
447 :
448 : nsresult rv;
449 557 : if (offset) {
450 6 : rv = Seek(PR_SEEK_SET, offset);
451 6 : if (NS_FAILED(rv)) return rv;
452 : }
453 557 : rv = SetEOF();
454 557 : if (NS_FAILED(rv)) return rv;
455 :
456 : // create a new output stream
457 557 : mOutStream = new nsDiskCacheOutputStream(this);
458 557 : if (!mOutStream) return NS_ERROR_OUT_OF_MEMORY;
459 :
460 557 : NS_ADDREF(*outputStream = mOutStream);
461 557 : return NS_OK;
462 : }
463 :
464 : nsresult
465 710 : nsDiskCacheStreamIO::ClearBinding()
466 : {
467 710 : nsresult rv = NS_OK;
468 710 : if (mBinding && mOutStream)
469 0 : rv = Flush();
470 710 : mBinding = nsnull;
471 710 : return rv;
472 : }
473 :
474 : nsresult
475 555 : nsDiskCacheStreamIO::CloseOutputStream(nsDiskCacheOutputStream * outputStream)
476 : {
477 1110 : nsCacheServiceAutoLock lock; // grab service lock
478 555 : return CloseOutputStreamInternal(outputStream);
479 : }
480 :
481 : nsresult
482 557 : nsDiskCacheStreamIO::CloseOutputStreamInternal(
483 : nsDiskCacheOutputStream * outputStream)
484 : {
485 : nsresult rv;
486 :
487 557 : if (outputStream != mOutStream) {
488 0 : NS_WARNING("mismatched output streams");
489 0 : return NS_ERROR_UNEXPECTED;
490 : }
491 :
492 : // output stream is closing
493 557 : if (!mBinding) { // if we're severed, just clear member variables
494 0 : NS_ASSERTION(!mBufDirty, "oops");
495 0 : mOutStream = nsnull;
496 0 : outputStream->ReleaseStreamIO();
497 0 : return NS_ERROR_NOT_AVAILABLE;
498 : }
499 :
500 557 : rv = Flush();
501 557 : if (NS_FAILED(rv))
502 0 : NS_WARNING("Flush() failed");
503 :
504 557 : mOutStream = nsnull;
505 557 : return rv;
506 : }
507 :
508 : nsresult
509 557 : nsDiskCacheStreamIO::Flush()
510 : {
511 557 : NS_ASSERTION(mBinding, "oops");
512 :
513 557 : CACHE_LOG_DEBUG(("CACHE: Flush [%x doomed=%u]\n",
514 : mBinding->mRecord.HashNumber(), mBinding->mDoomed));
515 :
516 557 : if (!mBufDirty) {
517 43 : if (mFD) {
518 3 : (void) PR_Close(mFD);
519 3 : mFD = nsnull;
520 : }
521 43 : return NS_OK;
522 : }
523 :
524 : // write data to cache blocks, or flush mBuffer to file
525 514 : nsDiskCacheMap *cacheMap = mDevice->CacheMap(); // get map reference
526 : nsresult rv;
527 :
528 514 : bool written = false;
529 :
530 990 : if ((mStreamEnd <= kMaxBufferSize) &&
531 476 : (mBinding->mCacheEntry->StoragePolicy() != nsICache::STORE_ON_DISK_AS_FILE)) {
532 : // store data (if any) in cache block files
533 :
534 476 : mBufDirty = false;
535 :
536 : // delete existing storage
537 476 : nsDiskCacheRecord * record = &mBinding->mRecord;
538 476 : if (record->DataLocationInitialized()) {
539 8 : rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
540 8 : if (NS_FAILED(rv)) {
541 0 : NS_WARNING("cacheMap->DeleteStorage() failed.");
542 0 : return rv;
543 : }
544 : }
545 :
546 : // flush buffer to block files
547 476 : written = true;
548 476 : if (mStreamEnd > 0) {
549 476 : rv = cacheMap->WriteDataCacheBlocks(mBinding, mBuffer, mBufEnd);
550 476 : if (NS_FAILED(rv)) {
551 0 : NS_WARNING("WriteDataCacheBlocks() failed.");
552 0 : written = false;
553 : }
554 : }
555 : }
556 :
557 514 : if (!written) {
558 : // make sure we save as separate file
559 38 : rv = FlushBufferToFile(); // initializes DataFileLocation() if necessary
560 :
561 38 : if (mFD) {
562 : // Update the file size of the disk file in the cache
563 38 : UpdateFileSize();
564 :
565 : // close file descriptor
566 38 : (void) PR_Close(mFD);
567 38 : mFD = nsnull;
568 : }
569 : else
570 0 : NS_WARNING("no file descriptor");
571 :
572 : // close mFD first if possible before returning if FlushBufferToFile
573 : // failed
574 38 : NS_ENSURE_SUCCESS(rv, rv);
575 :
576 : // since the data location is on disk as a single file, the only value
577 : // in keeping mBuffer around is to avoid an extra malloc the next time
578 : // we need to write to this file. reading will use a file descriptor.
579 : // therefore, it's probably not worth optimizing for the subsequent
580 : // write, so we unconditionally delete mBuffer here.
581 38 : DeleteBuffer();
582 : }
583 :
584 : // XXX do we need this here? WriteDataCacheBlocks() calls UpdateRecord()
585 : // update cache map if entry isn't doomed
586 514 : if (!mBinding->mDoomed) {
587 512 : rv = cacheMap->UpdateRecord(&mBinding->mRecord);
588 512 : if (NS_FAILED(rv)) {
589 0 : NS_WARNING("cacheMap->UpdateRecord() failed.");
590 0 : return rv; // XXX doom cache entry
591 : }
592 : }
593 :
594 514 : return NS_OK;
595 : }
596 :
597 :
598 : // assumptions:
599 : // only one thread writing at a time
600 : // never have both output and input streams open
601 : // OnDataSizeChanged() will have already been called to update entry->DataSize()
602 :
603 : nsresult
604 758 : nsDiskCacheStreamIO::Write( const char * buffer,
605 : PRUint32 count,
606 : PRUint32 * bytesWritten)
607 : {
608 758 : nsresult rv = NS_OK;
609 1516 : nsCacheServiceAutoLock lock; // grab service lock
610 758 : if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
611 :
612 758 : if (mInStreamCount) {
613 : // we have open input streams already
614 : // this is an error until we support overlapped I/O
615 0 : NS_WARNING("Attempting to write to cache entry with open input streams.\n");
616 0 : return NS_ERROR_NOT_AVAILABLE;
617 : }
618 :
619 758 : NS_ASSERTION(count, "Write called with count of zero");
620 758 : NS_ASSERTION(mBufPos <= mBufEnd, "streamIO buffer corrupted");
621 :
622 758 : PRUint32 bytesLeft = count;
623 758 : bool flushed = false;
624 :
625 9834 : while (bytesLeft) {
626 8318 : if (mBufPos == mBufSize) {
627 8216 : if (mBufSize < kMaxBufferSize) {
628 510 : mBufSize = kMaxBufferSize;
629 510 : char *buffer = mBuffer;
630 :
631 510 : mBuffer = (char *) realloc(mBuffer, mBufSize);
632 510 : if (!mBuffer) {
633 0 : free(buffer);
634 0 : mBufSize = 0;
635 0 : break;
636 : }
637 : } else {
638 7706 : nsresult rv = FlushBufferToFile();
639 7706 : if (NS_FAILED(rv)) break;
640 7706 : flushed = true;
641 : }
642 : }
643 :
644 8318 : PRUint32 chunkSize = bytesLeft;
645 8318 : if (chunkSize > (mBufSize - mBufPos))
646 7560 : chunkSize = mBufSize - mBufPos;
647 :
648 8318 : memcpy(mBuffer + mBufPos, buffer, chunkSize);
649 8318 : mBufDirty = true;
650 8318 : mBufPos += chunkSize;
651 8318 : bytesLeft -= chunkSize;
652 8318 : buffer += chunkSize;
653 :
654 8318 : if (mBufEnd < mBufPos)
655 8318 : mBufEnd = mBufPos;
656 : }
657 758 : if (bytesLeft) {
658 0 : *bytesWritten = 0;
659 0 : return NS_ERROR_FAILURE;
660 : }
661 758 : *bytesWritten = count;
662 :
663 : // update mStreamPos, mStreamEnd
664 758 : mStreamPos += count;
665 758 : if (mStreamEnd < mStreamPos) {
666 758 : mStreamEnd = mStreamPos;
667 758 : NS_ASSERTION(mBinding->mCacheEntry->DataSize() == mStreamEnd, "bad stream");
668 :
669 : // If we have flushed to a file, update the file size
670 758 : if (flushed && mFD) {
671 152 : UpdateFileSize();
672 : }
673 : }
674 :
675 758 : return rv;
676 : }
677 :
678 :
679 : void
680 194 : nsDiskCacheStreamIO::UpdateFileSize()
681 : {
682 194 : NS_ASSERTION(mFD, "nsDiskCacheStreamIO::UpdateFileSize should not have been called");
683 :
684 194 : nsDiskCacheRecord * record = &mBinding->mRecord;
685 194 : const PRUint32 oldSizeK = record->DataFileSize();
686 194 : PRUint32 newSizeK = (mStreamEnd + 0x03FF) >> 10;
687 :
688 : // make sure the size won't overflow (bug #651100)
689 194 : if (newSizeK > kMaxDataSizeK)
690 3 : newSizeK = kMaxDataSizeK;
691 :
692 194 : if (newSizeK == oldSizeK) return;
693 :
694 155 : record->SetDataFileSize(newSizeK);
695 :
696 : // update cache size totals
697 155 : nsDiskCacheMap * cacheMap = mDevice->CacheMap();
698 155 : cacheMap->DecrementTotalSize(oldSizeK); // decrement old size
699 155 : cacheMap->IncrementTotalSize(newSizeK); // increment new size
700 :
701 155 : if (!mBinding->mDoomed) {
702 138 : nsresult rv = cacheMap->UpdateRecord(record);
703 138 : if (NS_FAILED(rv)) {
704 0 : NS_WARNING("cacheMap->UpdateRecord() failed.");
705 : // XXX doom cache entry?
706 : }
707 : }
708 : }
709 :
710 :
711 : nsresult
712 42 : nsDiskCacheStreamIO::OpenCacheFile(PRIntn flags, PRFileDesc ** fd)
713 : {
714 42 : NS_ENSURE_ARG_POINTER(fd);
715 :
716 : nsresult rv;
717 42 : nsDiskCacheMap * cacheMap = mDevice->CacheMap();
718 :
719 : rv = cacheMap->GetLocalFileForDiskCacheRecord(&mBinding->mRecord,
720 : nsDiskCache::kData,
721 : !!(flags & PR_CREATE_FILE),
722 42 : getter_AddRefs(mLocalFile));
723 42 : if (NS_FAILED(rv)) return rv;
724 :
725 : // create PRFileDesc for input stream - the 00600 is just for consistency
726 42 : rv = mLocalFile->OpenNSPRFileDesc(flags, 00600, fd);
727 42 : if (NS_FAILED(rv)) return rv; // unable to open file
728 :
729 42 : return NS_OK;
730 : }
731 :
732 :
733 : nsresult
734 160 : nsDiskCacheStreamIO::ReadCacheBlocks()
735 : {
736 160 : NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "bad stream");
737 160 : NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "data too large for buffer");
738 :
739 160 : nsDiskCacheRecord * record = &mBinding->mRecord;
740 160 : if (!record->DataLocationInitialized()) return NS_OK;
741 :
742 160 : NS_ASSERTION(record->DataFile() != kSeparateFile, "attempt to read cache blocks on separate file");
743 :
744 160 : if (!mBuffer) {
745 : // allocate buffer
746 160 : mBuffer = (char *) malloc(mStreamEnd);
747 160 : if (!mBuffer) {
748 0 : return NS_ERROR_OUT_OF_MEMORY;
749 : }
750 160 : mBufSize = mStreamEnd;
751 : }
752 :
753 : // read data stored in cache block files
754 160 : nsDiskCacheMap *map = mDevice->CacheMap(); // get map reference
755 160 : nsresult rv = map->ReadDataCacheBlocks(mBinding, mBuffer, mStreamEnd);
756 160 : if (NS_FAILED(rv)) return rv;
757 :
758 : // update streamIO variables
759 160 : mBufPos = 0;
760 160 : mBufEnd = mStreamEnd;
761 :
762 160 : return NS_OK;
763 : }
764 :
765 :
766 : nsresult
767 7744 : nsDiskCacheStreamIO::FlushBufferToFile()
768 : {
769 : nsresult rv;
770 7744 : nsDiskCacheRecord * record = &mBinding->mRecord;
771 :
772 7744 : if (!mFD) {
773 37 : if (record->DataLocationInitialized() && (record->DataFile() > 0)) {
774 : // remove cache block storage
775 0 : nsDiskCacheMap * cacheMap = mDevice->CacheMap();
776 0 : rv = cacheMap->DeleteStorage(record, nsDiskCache::kData);
777 0 : if (NS_FAILED(rv)) return rv;
778 : }
779 37 : record->SetDataFileGeneration(mBinding->mGeneration);
780 :
781 : // allocate file
782 37 : rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
783 37 : if (NS_FAILED(rv)) return rv;
784 :
785 37 : PRInt64 dataSize = mBinding->mCacheEntry->PredictedDataSize();
786 37 : if (dataSize != -1)
787 32 : mozilla::fallocate(mFD, NS_MIN<PRInt64>(dataSize, kPreallocateLimit));
788 : }
789 :
790 : // write buffer
791 7744 : PRInt32 bytesWritten = PR_Write(mFD, mBuffer, mBufEnd);
792 7744 : if (PRUint32(bytesWritten) != mBufEnd) {
793 0 : NS_WARNING("failed to flush all data");
794 0 : return NS_ERROR_UNEXPECTED; // NS_ErrorAccordingToNSPR()
795 : }
796 7744 : mBufDirty = false;
797 :
798 : // reset buffer
799 7744 : mBufPos = 0;
800 7744 : mBufEnd = 0;
801 :
802 7744 : return NS_OK;
803 : }
804 :
805 :
806 : void
807 748 : nsDiskCacheStreamIO::DeleteBuffer()
808 : {
809 748 : if (mBuffer) {
810 668 : NS_ASSERTION(!mBufDirty, "deleting dirty buffer");
811 668 : free(mBuffer);
812 668 : mBuffer = nsnull;
813 668 : mBufPos = 0;
814 668 : mBufEnd = 0;
815 668 : mBufSize = 0;
816 : }
817 748 : }
818 :
819 :
820 : // NOTE: called with service lock held
821 : nsresult
822 6 : nsDiskCacheStreamIO::Seek(PRInt32 whence, PRInt32 offset)
823 : {
824 : PRInt32 newPos;
825 6 : if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
826 :
827 6 : if (PRUint32(offset) > mStreamEnd) return NS_ERROR_FAILURE;
828 :
829 6 : if (mBinding->mRecord.DataLocationInitialized()) {
830 6 : if (mBinding->mRecord.DataFile() == 0) {
831 4 : if (!mFD) {
832 : // we need an mFD, we better open it now
833 4 : nsresult rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
834 4 : if (NS_FAILED(rv)) return rv;
835 : }
836 : }
837 : }
838 :
839 6 : if (mFD) {
840 : // do we have data in the buffer that needs to be flushed?
841 4 : if (mBufDirty) {
842 : // XXX optimization: are we just moving within the current buffer?
843 0 : nsresult rv = FlushBufferToFile();
844 0 : if (NS_FAILED(rv)) return rv;
845 : }
846 :
847 4 : newPos = PR_Seek(mFD, offset, (PRSeekWhence)whence);
848 4 : if (newPos == -1)
849 0 : return NS_ErrorAccordingToNSPR();
850 :
851 4 : mStreamPos = (PRUint32) newPos;
852 4 : mBufPos = 0;
853 4 : mBufEnd = 0;
854 4 : return NS_OK;
855 : }
856 :
857 : // else, seek in mBuffer
858 :
859 2 : switch(whence) {
860 : case PR_SEEK_SET:
861 2 : newPos = offset;
862 2 : break;
863 :
864 : case PR_SEEK_CUR: // relative from current posistion
865 0 : newPos = offset + (PRUint32)mStreamPos;
866 0 : break;
867 :
868 : case PR_SEEK_END: // relative from end
869 0 : newPos = offset + (PRUint32)mBufEnd;
870 0 : break;
871 :
872 : default:
873 0 : return NS_ERROR_INVALID_ARG;
874 : }
875 :
876 : // read data into mBuffer if not read yet.
877 2 : if (mStreamEnd && !mBufEnd) {
878 2 : if (newPos > 0) {
879 2 : nsresult rv = ReadCacheBlocks();
880 2 : if (NS_FAILED(rv)) return rv;
881 : }
882 : }
883 :
884 : // stream buffer sanity checks
885 2 : NS_ASSERTION(mBufEnd <= kMaxBufferSize, "bad stream");
886 2 : NS_ASSERTION(mBufPos <= mBufEnd, "bad stream");
887 2 : NS_ASSERTION(mStreamPos == mBufPos, "bad stream");
888 2 : NS_ASSERTION(mStreamEnd == mBufEnd, "bad stream");
889 :
890 2 : if ((newPos < 0) || (PRUint32(newPos) > mBufEnd)) {
891 0 : NS_WARNING("seek offset out of range");
892 0 : return NS_ERROR_INVALID_ARG;
893 : }
894 :
895 2 : mStreamPos = newPos;
896 2 : mBufPos = newPos;
897 2 : return NS_OK;
898 : }
899 :
900 :
901 : // called only from nsDiskCacheOutputStream::Tell
902 : nsresult
903 0 : nsDiskCacheStreamIO::Tell(PRUint32 * result)
904 : {
905 0 : NS_ENSURE_ARG_POINTER(result);
906 0 : *result = mStreamPos;
907 0 : return NS_OK;
908 : }
909 :
910 :
911 : // NOTE: called with service lock held
912 : nsresult
913 557 : nsDiskCacheStreamIO::SetEOF()
914 : {
915 : nsresult rv;
916 557 : bool needToCloseFD = false;
917 :
918 557 : NS_ASSERTION(mStreamPos <= mStreamEnd, "bad stream");
919 557 : if (!mBinding) return NS_ERROR_NOT_AVAILABLE;
920 :
921 557 : if (mBinding->mRecord.DataLocationInitialized()) {
922 12 : if (mBinding->mRecord.DataFile() == 0) {
923 4 : if (!mFD) {
924 : // we need an mFD, we better open it now
925 0 : rv = OpenCacheFile(PR_RDWR | PR_CREATE_FILE, &mFD);
926 0 : if (NS_FAILED(rv)) return rv;
927 0 : needToCloseFD = true;
928 : }
929 : } else {
930 : // data in cache block files
931 8 : if ((mStreamPos != 0) && (mStreamPos != mBufPos)) {
932 : // only read data if there will be some left after truncation
933 0 : rv = ReadCacheBlocks();
934 0 : if (NS_FAILED(rv)) return rv;
935 : }
936 :
937 : // We need to make sure we reflect this change in Flush().
938 : // In particular, if mStreamPos is 0 and we never write to
939 : // the buffer, we want the storage to be deleted.
940 8 : mBufDirty = true;
941 : }
942 : }
943 :
944 557 : if (mFD) {
945 4 : rv = nsDiskCache::Truncate(mFD, mStreamPos);
946 : #ifdef DEBUG
947 4 : PRUint32 oldSizeK = (mStreamEnd + 0x03FF) >> 10;
948 4 : NS_ASSERTION(mBinding->mRecord.DataFileSize() == oldSizeK, "bad disk cache entry size");
949 : } else {
950 : // data stored in buffer.
951 553 : NS_ASSERTION(mStreamEnd <= kMaxBufferSize, "buffer truncation inadequate");
952 553 : NS_ASSERTION(mBufPos == mStreamPos, "bad stream");
953 553 : NS_ASSERTION(mBuffer ? mBufEnd == mStreamEnd : true, "bad stream");
954 : #endif
955 : }
956 :
957 557 : NS_ASSERTION(mStreamEnd == mBinding->mCacheEntry->DataSize(), "cache entry not updated");
958 : // we expect nsCacheEntryDescriptor::TransportWrapper::OpenOutputStream()
959 : // to eventually update the cache entry
960 :
961 557 : mStreamEnd = mStreamPos;
962 557 : mBufEnd = mBufPos;
963 :
964 557 : if (mFD) {
965 4 : UpdateFileSize();
966 4 : if (needToCloseFD) {
967 0 : (void) PR_Close(mFD);
968 0 : mFD = nsnull;
969 : }
970 : }
971 :
972 557 : return NS_OK;
973 : }
|