Bug 1909613 - Enable <details name=''> everywhere, r=emilio
[gecko.git] / netwerk / base / nsFileStreams.cpp
blob161a251afea9ab7df9b11102f8dab2930eca34bb
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "ipc/IPCMessageUtils.h"
8 #if defined(XP_UNIX)
9 # include <unistd.h>
10 #elif defined(XP_WIN)
11 # include <windows.h>
12 # include "nsILocalFileWin.h"
13 #else
14 // XXX add necessary include file for ftruncate (or equivalent)
15 #endif
17 #include "private/pprio.h"
19 #include "nsFileStreams.h"
20 #include "nsIFile.h"
21 #include "nsReadLine.h"
22 #include "nsIClassInfoImpl.h"
23 #include "mozilla/ipc/InputStreamUtils.h"
24 #include "mozilla/ipc/RandomAccessStreamParams.h"
25 #include "mozilla/Unused.h"
26 #include "mozilla/FileUtils.h"
27 #include "mozilla/UniquePtr.h"
28 #include "nsNetCID.h"
29 #include "nsNetUtil.h"
30 #include "nsXULAppAPI.h"
32 using FileHandleType = mozilla::ipc::FileDescriptor::PlatformHandleType;
34 using namespace mozilla::ipc;
36 using mozilla::DebugOnly;
37 using mozilla::Maybe;
38 using mozilla::Nothing;
39 using mozilla::Some;
41 ////////////////////////////////////////////////////////////////////////////////
42 // nsFileStreamBase
44 nsFileStreamBase::~nsFileStreamBase() {
45 // We don't want to try to rewrind the stream when shutting down.
46 mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
48 Close();
51 NS_IMPL_ISUPPORTS(nsFileStreamBase, nsISeekableStream, nsITellableStream,
52 nsIFileMetadata)
54 NS_IMETHODIMP
55 nsFileStreamBase::Seek(int32_t whence, int64_t offset) {
56 nsresult rv = DoPendingOpen();
57 NS_ENSURE_SUCCESS(rv, rv);
59 int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence);
60 if (cnt == int64_t(-1)) {
61 return NS_ErrorAccordingToNSPR();
63 return NS_OK;
66 NS_IMETHODIMP
67 nsFileStreamBase::Tell(int64_t* result) {
68 if (mState == eDeferredOpen && !(mOpenParams.ioFlags & PR_APPEND)) {
69 *result = 0;
70 return NS_OK;
73 nsresult rv = DoPendingOpen();
74 NS_ENSURE_SUCCESS(rv, rv);
76 int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR);
77 if (cnt == int64_t(-1)) {
78 return NS_ErrorAccordingToNSPR();
80 *result = cnt;
81 return NS_OK;
84 NS_IMETHODIMP
85 nsFileStreamBase::SetEOF() {
86 nsresult rv = DoPendingOpen();
87 NS_ENSURE_SUCCESS(rv, rv);
89 #if defined(XP_UNIX)
90 // Some system calls require an EOF offset.
91 int64_t offset;
92 rv = Tell(&offset);
93 if (NS_FAILED(rv)) return rv;
94 #endif
96 #if defined(XP_UNIX)
97 if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
98 NS_ERROR("ftruncate failed");
99 return NS_ERROR_FAILURE;
101 #elif defined(XP_WIN)
102 if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(mFD))) {
103 NS_ERROR("SetEndOfFile failed");
104 return NS_ERROR_FAILURE;
106 #else
107 // XXX not implemented
108 #endif
110 return NS_OK;
113 NS_IMETHODIMP
114 nsFileStreamBase::GetSize(int64_t* _retval) {
115 nsresult rv = DoPendingOpen();
116 NS_ENSURE_SUCCESS(rv, rv);
118 PRFileInfo64 info;
119 if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
120 return NS_BASE_STREAM_OSERROR;
123 *_retval = int64_t(info.size);
125 return NS_OK;
128 NS_IMETHODIMP
129 nsFileStreamBase::GetLastModified(int64_t* _retval) {
130 nsresult rv = DoPendingOpen();
131 NS_ENSURE_SUCCESS(rv, rv);
133 PRFileInfo64 info;
134 if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
135 return NS_BASE_STREAM_OSERROR;
138 int64_t modTime = int64_t(info.modifyTime);
139 if (modTime == 0) {
140 *_retval = 0;
141 } else {
142 *_retval = modTime / int64_t(PR_USEC_PER_MSEC);
145 return NS_OK;
148 NS_IMETHODIMP
149 nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval) {
150 nsresult rv = DoPendingOpen();
151 NS_ENSURE_SUCCESS(rv, rv);
153 *_retval = mFD;
154 return NS_OK;
157 nsresult nsFileStreamBase::Close() {
158 if (mState == eClosed) {
159 return NS_OK;
162 CleanUpOpen();
164 nsresult rv = NS_OK;
165 if (mFD) {
166 if (PR_Close(mFD) == PR_FAILURE) rv = NS_BASE_STREAM_OSERROR;
167 mFD = nullptr;
168 mState = eClosed;
170 return rv;
173 nsresult nsFileStreamBase::Available(uint64_t* aResult) {
174 nsresult rv = DoPendingOpen();
175 NS_ENSURE_SUCCESS(rv, rv);
177 // PR_Available with files over 4GB returns an error, so we have to
178 // use the 64-bit version of PR_Available.
179 int64_t avail = PR_Available64(mFD);
180 if (avail == -1) {
181 return NS_ErrorAccordingToNSPR();
184 // If available is greater than 4GB, return 4GB
185 *aResult = (uint64_t)avail;
186 return NS_OK;
189 nsresult nsFileStreamBase::Read(char* aBuf, uint32_t aCount,
190 uint32_t* aResult) {
191 nsresult rv = DoPendingOpen();
192 if (rv == NS_BASE_STREAM_CLOSED) {
193 *aResult = 0;
194 return NS_OK;
197 if (NS_FAILED(rv)) {
198 return rv;
201 int32_t bytesRead = PR_Read(mFD, aBuf, aCount);
202 if (bytesRead == -1) {
203 return NS_ErrorAccordingToNSPR();
206 *aResult = bytesRead;
207 return NS_OK;
210 nsresult nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter,
211 void* aClosure, uint32_t aCount,
212 uint32_t* aResult) {
213 // ReadSegments is not implemented because it would be inefficient when
214 // the writer does not consume all data. If you want to call ReadSegments,
215 // wrap a BufferedInputStream around the file stream. That will call
216 // Read().
218 return NS_ERROR_NOT_IMPLEMENTED;
221 nsresult nsFileStreamBase::IsNonBlocking(bool* aNonBlocking) {
222 *aNonBlocking = false;
223 return NS_OK;
226 nsresult nsFileStreamBase::Flush(void) {
227 nsresult rv = DoPendingOpen();
228 NS_ENSURE_SUCCESS(rv, rv);
230 int32_t cnt = PR_Sync(mFD);
231 if (cnt == -1) {
232 return NS_ErrorAccordingToNSPR();
234 return NS_OK;
237 nsresult nsFileStreamBase::StreamStatus() {
238 switch (mState) {
239 case eUnitialized:
240 MOZ_CRASH("This should not happen.");
241 return NS_ERROR_FAILURE;
243 case eDeferredOpen:
244 return NS_OK;
246 case eOpened:
247 MOZ_ASSERT(mFD);
248 if (NS_WARN_IF(!mFD)) {
249 return NS_ERROR_FAILURE;
251 return NS_OK;
253 case eClosed:
254 MOZ_ASSERT(!mFD);
255 return NS_BASE_STREAM_CLOSED;
257 case eError:
258 return mErrorValue;
261 MOZ_CRASH("Invalid mState value.");
262 return NS_ERROR_FAILURE;
265 nsresult nsFileStreamBase::Write(const char* buf, uint32_t count,
266 uint32_t* result) {
267 nsresult rv = DoPendingOpen();
268 NS_ENSURE_SUCCESS(rv, rv);
270 int32_t cnt = PR_Write(mFD, buf, count);
271 if (cnt == -1) {
272 return NS_ErrorAccordingToNSPR();
274 *result = cnt;
275 return NS_OK;
278 nsresult nsFileStreamBase::WriteFrom(nsIInputStream* inStr, uint32_t count,
279 uint32_t* _retval) {
280 MOZ_ASSERT_UNREACHABLE("WriteFrom (see source comment)");
281 return NS_ERROR_NOT_IMPLEMENTED;
282 // File streams intentionally do not support this method.
283 // If you need something like this, then you should wrap
284 // the file stream using nsIBufferedOutputStream
287 nsresult nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void* closure,
288 uint32_t count, uint32_t* _retval) {
289 return NS_ERROR_NOT_IMPLEMENTED;
290 // File streams intentionally do not support this method.
291 // If you need something like this, then you should wrap
292 // the file stream using nsIBufferedOutputStream
295 nsresult nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags,
296 int32_t aPerm, bool aDeferred) {
297 NS_ENSURE_STATE(aFile);
299 mOpenParams.ioFlags = aIoFlags;
300 mOpenParams.perm = aPerm;
302 if (aDeferred) {
303 // Clone the file, as it may change between now and the deferred open
304 nsCOMPtr<nsIFile> file;
305 nsresult rv = aFile->Clone(getter_AddRefs(file));
306 NS_ENSURE_SUCCESS(rv, rv);
308 mOpenParams.localFile = std::move(file);
309 NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED);
311 mState = eDeferredOpen;
312 return NS_OK;
315 mOpenParams.localFile = aFile;
317 // Following call open() at main thread.
318 // Main thread might be blocked, while open a remote file.
319 return DoOpen();
322 void nsFileStreamBase::CleanUpOpen() { mOpenParams.localFile = nullptr; }
324 nsresult nsFileStreamBase::DoOpen() {
325 MOZ_ASSERT(mState == eDeferredOpen || mState == eUnitialized ||
326 mState == eClosed);
327 NS_ASSERTION(!mFD, "Already have a file descriptor!");
328 NS_ASSERTION(mOpenParams.localFile, "Must have a file to open");
330 PRFileDesc* fd;
331 nsresult rv;
333 if (mOpenParams.ioFlags & PR_CREATE_FILE) {
334 nsCOMPtr<nsIFile> parent;
335 mOpenParams.localFile->GetParent(getter_AddRefs(parent));
337 // Result doesn't need to be checked. If the file's parent path does not
338 // exist, make it. If it does exist, do nothing.
339 if (parent) {
340 mozilla::Unused << parent->Create(nsIFile::DIRECTORY_TYPE, 0755);
344 #ifdef XP_WIN
345 if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) {
346 nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(mOpenParams.localFile);
347 MOZ_ASSERT(file);
349 rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags,
350 mOpenParams.perm, &fd);
351 } else
352 #endif // XP_WIN
354 rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags,
355 mOpenParams.perm, &fd);
358 CleanUpOpen();
360 if (NS_FAILED(rv)) {
361 mState = eError;
362 mErrorValue = rv;
363 return rv;
366 mFD = fd;
367 mState = eOpened;
369 return NS_OK;
372 nsresult nsFileStreamBase::DoPendingOpen() {
373 switch (mState) {
374 case eUnitialized:
375 MOZ_CRASH("This should not happen.");
376 return NS_ERROR_FAILURE;
378 case eDeferredOpen:
379 return DoOpen();
381 case eOpened:
382 MOZ_ASSERT(mFD);
383 if (NS_WARN_IF(!mFD)) {
384 return NS_ERROR_FAILURE;
386 return NS_OK;
388 case eClosed:
389 MOZ_ASSERT(!mFD);
390 return NS_BASE_STREAM_CLOSED;
392 case eError:
393 return mErrorValue;
396 MOZ_CRASH("Invalid mState value.");
397 return NS_ERROR_FAILURE;
400 ////////////////////////////////////////////////////////////////////////////////
401 // nsFileInputStream
403 NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase)
404 NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase)
406 NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE,
407 NS_LOCALFILEINPUTSTREAM_CID)
409 NS_INTERFACE_MAP_BEGIN(nsFileInputStream)
410 NS_INTERFACE_MAP_ENTRY(nsIInputStream)
411 NS_INTERFACE_MAP_ENTRY(nsIFileInputStream)
412 NS_INTERFACE_MAP_ENTRY(nsILineInputStream)
413 NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
414 NS_IMPL_QUERY_CLASSINFO(nsFileInputStream)
415 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable())
416 NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
418 NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream, nsIInputStream,
419 nsIFileInputStream, nsISeekableStream,
420 nsITellableStream, nsILineInputStream)
422 nsresult nsFileInputStream::Create(REFNSIID aIID, void** aResult) {
423 RefPtr<nsFileInputStream> stream = new nsFileInputStream();
424 return stream->QueryInterface(aIID, aResult);
427 nsresult nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags,
428 int32_t aPerm) {
429 nsresult rv = NS_OK;
431 // If the previous file is open, close it
432 if (mFD) {
433 rv = Close();
434 if (NS_FAILED(rv)) return rv;
437 // Open the file
438 if (aIOFlags == -1) aIOFlags = PR_RDONLY;
439 if (aPerm == -1) aPerm = 0;
441 return MaybeOpen(aFile, aIOFlags, aPerm,
442 mBehaviorFlags & nsIFileInputStream::DEFER_OPEN);
445 NS_IMETHODIMP
446 nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
447 int32_t aBehaviorFlags) {
448 NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED);
449 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
450 NS_ERROR_ALREADY_INITIALIZED);
452 mBehaviorFlags = aBehaviorFlags;
453 mState = eUnitialized;
455 mFile = aFile;
456 mIOFlags = aIOFlags;
457 mPerm = aPerm;
459 return Open(aFile, aIOFlags, aPerm);
462 NS_IMETHODIMP
463 nsFileInputStream::Close() {
464 // If this stream has already been closed, do nothing.
465 if (mState == eClosed) {
466 return NS_OK;
469 // Get the cache position at the time the file was close. This allows
470 // NS_SEEK_CUR on a closed file that has been opened with
471 // REOPEN_ON_REWIND.
472 if (mBehaviorFlags & REOPEN_ON_REWIND) {
473 // Get actual position. Not one modified by subclasses
474 nsFileStreamBase::Tell(&mCachedPosition);
477 // explicitly clear mLineBuffer in case this stream is reopened
478 mLineBuffer = nullptr;
479 return nsFileStreamBase::Close();
482 NS_IMETHODIMP
483 nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
484 nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval);
485 if (rv == NS_ERROR_FILE_NOT_FOUND) {
486 // Don't warn if this is a deffered file not found.
487 return rv;
490 if (NS_FAILED(rv)) {
491 return rv;
494 // Check if we're at the end of file and need to close
495 if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) {
496 Close();
499 return NS_OK;
502 NS_IMETHODIMP
503 nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult) {
504 if (!mLineBuffer) {
505 mLineBuffer = mozilla::MakeUnique<nsLineBuffer<char>>();
507 return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult);
510 NS_IMETHODIMP
511 nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset) {
512 return SeekInternal(aWhence, aOffset);
515 nsresult nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset,
516 bool aClearBuf) {
517 nsresult rv = DoPendingOpen();
518 if (rv != NS_OK && rv != NS_BASE_STREAM_CLOSED) {
519 return rv;
522 if (aClearBuf) {
523 mLineBuffer = nullptr;
526 if (rv == NS_BASE_STREAM_CLOSED) {
527 if (mBehaviorFlags & REOPEN_ON_REWIND) {
528 rv = Open(mFile, mIOFlags, mPerm);
529 NS_ENSURE_SUCCESS(rv, rv);
531 // If the file was closed, and we do a relative seek, use the
532 // position we cached when we closed the file to seek to the right
533 // location.
534 if (aWhence == NS_SEEK_CUR) {
535 aWhence = NS_SEEK_SET;
536 aOffset += mCachedPosition;
538 // If we're trying to seek to the start then we're done, so
539 // return early to avoid Seek from calling DoPendingOpen and
540 // opening the underlying file earlier than necessary.
541 if (aWhence == NS_SEEK_SET && aOffset == 0) {
542 return NS_OK;
544 } else {
545 return NS_BASE_STREAM_CLOSED;
549 return nsFileStreamBase::Seek(aWhence, aOffset);
552 NS_IMETHODIMP
553 nsFileInputStream::Tell(int64_t* aResult) {
554 return nsFileStreamBase::Tell(aResult);
557 NS_IMETHODIMP
558 nsFileInputStream::Available(uint64_t* aResult) {
559 return nsFileStreamBase::Available(aResult);
562 NS_IMETHODIMP
563 nsFileInputStream::StreamStatus() { return nsFileStreamBase::StreamStatus(); }
565 void nsFileInputStream::SerializedComplexity(uint32_t aMaxSize,
566 uint32_t* aSizeUsed,
567 uint32_t* aPipes,
568 uint32_t* aTransferables) {
569 *aTransferables = 1;
572 void nsFileInputStream::Serialize(InputStreamParams& aParams, uint32_t aMaxSize,
573 uint32_t* aSizeUsed) {
574 MOZ_ASSERT(aSizeUsed);
575 *aSizeUsed = 0;
577 FileInputStreamParams params;
579 if (NS_SUCCEEDED(DoPendingOpen())) {
580 MOZ_ASSERT(mFD);
581 FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
582 NS_ASSERTION(fd, "This should never be null!");
584 params.fileDescriptor() = FileDescriptor(fd);
586 Close();
587 } else {
588 NS_WARNING(
589 "This file has not been opened (or could not be opened). "
590 "Sending an invalid file descriptor to the other process!");
592 params.fileDescriptor() = FileDescriptor();
595 int32_t behaviorFlags = mBehaviorFlags;
597 // The receiving process (or thread) is going to have an open file
598 // descriptor automatically so transferring this flag is meaningless.
599 behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
601 params.behaviorFlags() = behaviorFlags;
602 params.ioFlags() = mIOFlags;
604 aParams = params;
607 bool nsFileInputStream::Deserialize(const InputStreamParams& aParams) {
608 NS_ASSERTION(!mFD, "Already have a file descriptor?!");
609 NS_ASSERTION(mState == nsFileStreamBase::eUnitialized, "Deferring open?!");
610 NS_ASSERTION(!mFile, "Should never have a file here!");
611 NS_ASSERTION(!mPerm, "This should always be 0!");
613 if (aParams.type() != InputStreamParams::TFileInputStreamParams) {
614 NS_WARNING("Received unknown parameters from the other process!");
615 return false;
618 const FileInputStreamParams& params = aParams.get_FileInputStreamParams();
620 const FileDescriptor& fd = params.fileDescriptor();
622 if (fd.IsValid()) {
623 auto rawFD = fd.ClonePlatformHandle();
624 PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
625 if (!fileDesc) {
626 NS_WARNING("Failed to import file handle!");
627 return false;
629 mFD = fileDesc;
630 mState = eOpened;
631 } else {
632 NS_WARNING("Received an invalid file descriptor!");
633 mState = eError;
634 mErrorValue = NS_ERROR_FILE_NOT_FOUND;
637 mBehaviorFlags = params.behaviorFlags();
639 if (!XRE_IsParentProcess()) {
640 // A child process shouldn't close when it reads the end because it will
641 // not be able to reopen the file later.
642 mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF;
644 // A child process will not be able to reopen the file so this flag is
645 // meaningless.
646 mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
649 mIOFlags = params.ioFlags();
651 return true;
654 bool nsFileInputStream::IsCloneable() const {
655 // This inputStream is cloneable only if has been created using Init() and
656 // it owns a nsIFile. This is not true when it is deserialized from IPC.
657 return XRE_IsParentProcess() && mFile;
660 NS_IMETHODIMP
661 nsFileInputStream::GetCloneable(bool* aCloneable) {
662 *aCloneable = IsCloneable();
663 return NS_OK;
666 NS_IMETHODIMP
667 nsFileInputStream::Clone(nsIInputStream** aResult) {
668 MOZ_ASSERT(IsCloneable());
669 return NS_NewLocalFileInputStream(aResult, mFile, mIOFlags, mPerm,
670 mBehaviorFlags);
673 ////////////////////////////////////////////////////////////////////////////////
674 // nsFileOutputStream
676 NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream, nsFileStreamBase,
677 nsIOutputStream, nsIFileOutputStream)
679 nsresult nsFileOutputStream::Create(REFNSIID aIID, void** aResult) {
680 RefPtr<nsFileOutputStream> stream = new nsFileOutputStream();
681 return stream->QueryInterface(aIID, aResult);
684 NS_IMETHODIMP
685 nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
686 int32_t behaviorFlags) {
687 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
688 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
689 NS_ERROR_ALREADY_INITIALIZED);
691 mBehaviorFlags = behaviorFlags;
692 mState = eUnitialized;
694 if (ioFlags == -1) ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
695 if (perm <= 0) perm = 0664;
697 return MaybeOpen(file, ioFlags, perm,
698 mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN);
701 nsresult nsFileOutputStream::InitWithFileDescriptor(
702 const mozilla::ipc::FileDescriptor& aFd) {
703 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
704 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
705 NS_ERROR_ALREADY_INITIALIZED);
707 if (aFd.IsValid()) {
708 auto rawFD = aFd.ClonePlatformHandle();
709 PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
710 if (!fileDesc) {
711 NS_WARNING("Failed to import file handle!");
712 return NS_ERROR_FAILURE;
714 mFD = fileDesc;
715 mState = eOpened;
716 } else {
717 mState = eError;
718 mErrorValue = NS_ERROR_FILE_NOT_FOUND;
721 return NS_OK;
724 NS_IMETHODIMP
725 nsFileOutputStream::Preallocate(int64_t aLength) {
726 if (!mFD) {
727 return NS_ERROR_NOT_INITIALIZED;
730 if (!mozilla::fallocate(mFD, aLength)) {
731 return NS_ERROR_FAILURE;
734 return NS_OK;
737 ////////////////////////////////////////////////////////////////////////////////
738 // nsAtomicFileOutputStream
740 NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream, nsFileOutputStream,
741 nsISafeOutputStream, nsIOutputStream,
742 nsIFileOutputStream)
744 NS_IMETHODIMP
745 nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
746 int32_t behaviorFlags) {
747 // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter
748 // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending
749 // to existing file. So, throw an exception only if `PR_APPEND` is
750 // explicitly specified without `PR_TRUNCATE`.
751 if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) {
752 return NS_ERROR_INVALID_ARG;
754 return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags);
757 nsresult nsAtomicFileOutputStream::DoOpen() {
758 // Make sure mOpenParams.localFile will be empty if we bail somewhere in
759 // this function
760 nsCOMPtr<nsIFile> file;
761 file.swap(mOpenParams.localFile);
763 if (!file) {
764 return NS_ERROR_NOT_INITIALIZED;
766 nsresult rv = file->Exists(&mTargetFileExists);
767 if (NS_FAILED(rv)) {
768 NS_ERROR("Can't tell if target file exists");
769 mTargetFileExists =
770 true; // Safer to assume it exists - we just do more work.
773 // follow symlinks, for two reasons:
774 // 1) if a user has deliberately set up a profile file as a symlink, we
775 // honor it
776 // 2) to make the MoveToNative() in Finish() an atomic operation (which may
777 // not be the case if moving across directories on different
778 // filesystems).
779 nsCOMPtr<nsIFile> tempResult;
780 rv = file->Clone(getter_AddRefs(tempResult));
781 if (NS_SUCCEEDED(rv) && mTargetFileExists) {
782 tempResult->Normalize();
785 if (NS_SUCCEEDED(rv) && mTargetFileExists) {
786 // Abort if |file| is not writable; it won't work as an output stream.
787 bool isWritable;
788 if (NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) {
789 return NS_ERROR_FILE_ACCESS_DENIED;
792 uint32_t origPerm;
793 if (NS_FAILED(file->GetPermissions(&origPerm))) {
794 NS_ERROR("Can't get permissions of target file");
795 origPerm = mOpenParams.perm;
798 // XXX What if |perm| is more restrictive then |origPerm|?
799 // This leaves the user supplied permissions as they were.
800 rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm);
802 if (NS_SUCCEEDED(rv)) {
803 // nsFileOutputStream::DoOpen will work on the temporary file, so we
804 // prepare it and place it in mOpenParams.localFile.
805 mOpenParams.localFile = tempResult;
806 mTempFile = tempResult;
807 mTargetFile = file;
808 rv = nsFileOutputStream::DoOpen();
810 return rv;
813 NS_IMETHODIMP
814 nsAtomicFileOutputStream::Close() {
815 nsresult rv = nsFileOutputStream::Close();
817 // the consumer doesn't want the original file overwritten -
818 // so clean up by removing the temp file.
819 if (mTempFile) {
820 mTempFile->Remove(false);
821 mTempFile = nullptr;
824 return rv;
827 NS_IMETHODIMP
828 nsAtomicFileOutputStream::Finish() {
829 nsresult rv = nsFileOutputStream::Close();
831 // if there is no temp file, don't try to move it over the original target.
832 // It would destroy the targetfile if close() is called twice.
833 if (!mTempFile) return rv;
835 // Only overwrite if everything was ok, and the temp file could be closed.
836 if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) {
837 NS_ENSURE_STATE(mTargetFile);
839 if (!mTargetFileExists) {
840 // If the target file did not exist when we were initialized, then the
841 // temp file we gave out was actually a reference to the target file.
842 // since we succeeded in writing to the temp file (and hence succeeded
843 // in writing to the target file), there is nothing more to do.
844 #ifdef DEBUG
845 bool equal;
846 if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal) {
847 NS_WARNING("mTempFile not equal to mTargetFile");
849 #endif
850 } else {
851 nsAutoString targetFilename;
852 rv = mTargetFile->GetLeafName(targetFilename);
853 if (NS_SUCCEEDED(rv)) {
854 // This will replace target.
855 rv = mTempFile->MoveTo(nullptr, targetFilename);
856 if (NS_FAILED(rv)) mTempFile->Remove(false);
859 } else {
860 mTempFile->Remove(false);
862 // if writing failed, propagate the failure code to the caller.
863 if (NS_FAILED(mWriteResult)) rv = mWriteResult;
865 mTempFile = nullptr;
866 return rv;
869 NS_IMETHODIMP
870 nsAtomicFileOutputStream::Write(const char* buf, uint32_t count,
871 uint32_t* result) {
872 nsresult rv = nsFileOutputStream::Write(buf, count, result);
873 if (NS_SUCCEEDED(mWriteResult)) {
874 if (NS_FAILED(rv)) {
875 mWriteResult = rv;
876 } else if (count != *result) {
877 mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
880 if (NS_FAILED(mWriteResult) && count > 0) {
881 NS_WARNING("writing to output stream failed! data may be lost");
884 return rv;
887 ////////////////////////////////////////////////////////////////////////////////
888 // nsSafeFileOutputStream
890 NS_IMETHODIMP
891 nsSafeFileOutputStream::Finish() {
892 (void)Flush();
893 return nsAtomicFileOutputStream::Finish();
896 ////////////////////////////////////////////////////////////////////////////////
897 // nsFileRandomAccessStream
899 nsresult nsFileRandomAccessStream::Create(REFNSIID aIID, void** aResult) {
900 RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream();
901 return stream->QueryInterface(aIID, aResult);
904 NS_IMPL_ISUPPORTS_INHERITED(nsFileRandomAccessStream, nsFileStreamBase,
905 nsIRandomAccessStream, nsIFileRandomAccessStream,
906 nsIInputStream, nsIOutputStream)
908 NS_IMETHODIMP
909 nsFileRandomAccessStream::GetInputStream(nsIInputStream** aInputStream) {
910 nsCOMPtr<nsIInputStream> inputStream(this);
912 inputStream.forget(aInputStream);
913 return NS_OK;
916 NS_IMETHODIMP
917 nsFileRandomAccessStream::GetOutputStream(nsIOutputStream** aOutputStream) {
918 nsCOMPtr<nsIOutputStream> outputStream(this);
920 outputStream.forget(aOutputStream);
921 return NS_OK;
924 nsIInputStream* nsFileRandomAccessStream::InputStream() { return this; }
926 nsIOutputStream* nsFileRandomAccessStream::OutputStream() { return this; }
928 RandomAccessStreamParams nsFileRandomAccessStream::Serialize(
929 nsIInterfaceRequestor* aCallbacks) {
930 FileRandomAccessStreamParams params;
932 if (NS_SUCCEEDED(DoPendingOpen())) {
933 MOZ_ASSERT(mFD);
934 FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
935 MOZ_ASSERT(fd, "This should never be null!");
937 params.fileDescriptor() = FileDescriptor(fd);
939 Close();
940 } else {
941 NS_WARNING(
942 "This file has not been opened (or could not be opened). "
943 "Sending an invalid file descriptor to the other process!");
945 params.fileDescriptor() = FileDescriptor();
948 int32_t behaviorFlags = mBehaviorFlags;
950 // The receiving process (or thread) is going to have an open file
951 // descriptor automatically so transferring this flag is meaningless.
952 behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
954 params.behaviorFlags() = behaviorFlags;
956 return params;
959 bool nsFileRandomAccessStream::Deserialize(
960 RandomAccessStreamParams& aStreamParams) {
961 MOZ_ASSERT(!mFD, "Already have a file descriptor?!");
962 MOZ_ASSERT(mState == nsFileStreamBase::eUnitialized, "Deferring open?!");
964 if (aStreamParams.type() !=
965 RandomAccessStreamParams::TFileRandomAccessStreamParams) {
966 NS_WARNING("Received unknown parameters from the other process!");
967 return false;
970 const FileRandomAccessStreamParams& params =
971 aStreamParams.get_FileRandomAccessStreamParams();
973 const FileDescriptor& fd = params.fileDescriptor();
975 if (fd.IsValid()) {
976 auto rawFD = fd.ClonePlatformHandle();
977 PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
978 if (!fileDesc) {
979 NS_WARNING("Failed to import file handle!");
980 return false;
982 mFD = fileDesc;
983 mState = eOpened;
984 } else {
985 NS_WARNING("Received an invalid file descriptor!");
986 mState = eError;
987 mErrorValue = NS_ERROR_FILE_NOT_FOUND;
990 mBehaviorFlags = params.behaviorFlags();
992 return true;
995 NS_IMETHODIMP
996 nsFileRandomAccessStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
997 int32_t behaviorFlags) {
998 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
999 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
1000 NS_ERROR_ALREADY_INITIALIZED);
1002 mBehaviorFlags = behaviorFlags;
1003 mState = eUnitialized;
1005 if (ioFlags == -1) ioFlags = PR_RDWR;
1006 if (perm <= 0) perm = 0;
1008 return MaybeOpen(file, ioFlags, perm,
1009 mBehaviorFlags & nsIFileRandomAccessStream::DEFER_OPEN);
1012 ////////////////////////////////////////////////////////////////////////////////