Bug 1804798 - Explicitly set auto page name (and corresponding debug flag) when inser...
[gecko.git] / netwerk / base / nsFileStreams.cpp
blob742f1482ba4bcc454037dfbff315dd824f9f11b0
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"
18 #include "prerror.h"
20 #include "IOActivityMonitor.h"
21 #include "nsFileStreams.h"
22 #include "nsIFile.h"
23 #include "nsReadLine.h"
24 #include "nsIClassInfoImpl.h"
25 #include "nsLiteralString.h"
26 #include "nsSocketTransport2.h" // for ErrorAccordingToNSPR()
27 #include "mozilla/ipc/InputStreamUtils.h"
28 #include "mozilla/ipc/RandomAccessStreamParams.h"
29 #include "mozilla/Unused.h"
30 #include "mozilla/FileUtils.h"
31 #include "nsNetCID.h"
32 #include "nsXULAppAPI.h"
34 using FileHandleType = mozilla::ipc::FileDescriptor::PlatformHandleType;
36 using namespace mozilla::ipc;
37 using namespace mozilla::net;
39 using mozilla::DebugOnly;
40 using mozilla::Maybe;
41 using mozilla::Nothing;
42 using mozilla::Some;
44 ////////////////////////////////////////////////////////////////////////////////
45 // nsFileStreamBase
47 nsFileStreamBase::~nsFileStreamBase() {
48 // We don't want to try to rewrind the stream when shutting down.
49 mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
51 Close();
54 NS_IMPL_ISUPPORTS(nsFileStreamBase, nsISeekableStream, nsITellableStream,
55 nsIFileMetadata)
57 NS_IMETHODIMP
58 nsFileStreamBase::Seek(int32_t whence, int64_t offset) {
59 nsresult rv = DoPendingOpen();
60 NS_ENSURE_SUCCESS(rv, rv);
62 int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence);
63 if (cnt == int64_t(-1)) {
64 return NS_ErrorAccordingToNSPR();
66 return NS_OK;
69 NS_IMETHODIMP
70 nsFileStreamBase::Tell(int64_t* result) {
71 if (mState == eDeferredOpen && !(mOpenParams.ioFlags & PR_APPEND)) {
72 *result = 0;
73 return NS_OK;
76 nsresult rv = DoPendingOpen();
77 NS_ENSURE_SUCCESS(rv, rv);
79 int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR);
80 if (cnt == int64_t(-1)) {
81 return NS_ErrorAccordingToNSPR();
83 *result = cnt;
84 return NS_OK;
87 NS_IMETHODIMP
88 nsFileStreamBase::SetEOF() {
89 nsresult rv = DoPendingOpen();
90 NS_ENSURE_SUCCESS(rv, rv);
92 #if defined(XP_UNIX)
93 // Some system calls require an EOF offset.
94 int64_t offset;
95 rv = Tell(&offset);
96 if (NS_FAILED(rv)) return rv;
97 #endif
99 #if defined(XP_UNIX)
100 if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
101 NS_ERROR("ftruncate failed");
102 return NS_ERROR_FAILURE;
104 #elif defined(XP_WIN)
105 if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(mFD))) {
106 NS_ERROR("SetEndOfFile failed");
107 return NS_ERROR_FAILURE;
109 #else
110 // XXX not implemented
111 #endif
113 return NS_OK;
116 NS_IMETHODIMP
117 nsFileStreamBase::GetSize(int64_t* _retval) {
118 nsresult rv = DoPendingOpen();
119 NS_ENSURE_SUCCESS(rv, rv);
121 PRFileInfo64 info;
122 if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
123 return NS_BASE_STREAM_OSERROR;
126 *_retval = int64_t(info.size);
128 return NS_OK;
131 NS_IMETHODIMP
132 nsFileStreamBase::GetLastModified(int64_t* _retval) {
133 nsresult rv = DoPendingOpen();
134 NS_ENSURE_SUCCESS(rv, rv);
136 PRFileInfo64 info;
137 if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
138 return NS_BASE_STREAM_OSERROR;
141 int64_t modTime = int64_t(info.modifyTime);
142 if (modTime == 0) {
143 *_retval = 0;
144 } else {
145 *_retval = modTime / int64_t(PR_USEC_PER_MSEC);
148 return NS_OK;
151 NS_IMETHODIMP
152 nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval) {
153 nsresult rv = DoPendingOpen();
154 NS_ENSURE_SUCCESS(rv, rv);
156 *_retval = mFD;
157 return NS_OK;
160 nsresult nsFileStreamBase::Close() {
161 CleanUpOpen();
163 nsresult rv = NS_OK;
164 if (mFD) {
165 if (PR_Close(mFD) == PR_FAILURE) rv = NS_BASE_STREAM_OSERROR;
166 mFD = nullptr;
167 mState = eClosed;
169 return rv;
172 nsresult nsFileStreamBase::Available(uint64_t* aResult) {
173 nsresult rv = DoPendingOpen();
174 NS_ENSURE_SUCCESS(rv, rv);
176 // PR_Available with files over 4GB returns an error, so we have to
177 // use the 64-bit version of PR_Available.
178 int64_t avail = PR_Available64(mFD);
179 if (avail == -1) {
180 return NS_ErrorAccordingToNSPR();
183 // If available is greater than 4GB, return 4GB
184 *aResult = (uint64_t)avail;
185 return NS_OK;
188 nsresult nsFileStreamBase::Read(char* aBuf, uint32_t aCount,
189 uint32_t* aResult) {
190 nsresult rv = DoPendingOpen();
191 if (rv == NS_BASE_STREAM_CLOSED) {
192 *aResult = 0;
193 return NS_OK;
196 if (NS_FAILED(rv)) {
197 return rv;
200 int32_t bytesRead = PR_Read(mFD, aBuf, aCount);
201 if (bytesRead == -1) {
202 return NS_ErrorAccordingToNSPR();
205 *aResult = bytesRead;
206 return NS_OK;
209 nsresult nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter,
210 void* aClosure, uint32_t aCount,
211 uint32_t* aResult) {
212 // ReadSegments is not implemented because it would be inefficient when
213 // the writer does not consume all data. If you want to call ReadSegments,
214 // wrap a BufferedInputStream around the file stream. That will call
215 // Read().
217 return NS_ERROR_NOT_IMPLEMENTED;
220 nsresult nsFileStreamBase::IsNonBlocking(bool* aNonBlocking) {
221 *aNonBlocking = false;
222 return NS_OK;
225 nsresult nsFileStreamBase::Flush(void) {
226 nsresult rv = DoPendingOpen();
227 NS_ENSURE_SUCCESS(rv, rv);
229 int32_t cnt = PR_Sync(mFD);
230 if (cnt == -1) {
231 return NS_ErrorAccordingToNSPR();
233 return NS_OK;
236 nsresult nsFileStreamBase::Write(const char* buf, uint32_t count,
237 uint32_t* result) {
238 nsresult rv = DoPendingOpen();
239 NS_ENSURE_SUCCESS(rv, rv);
241 int32_t cnt = PR_Write(mFD, buf, count);
242 if (cnt == -1) {
243 return NS_ErrorAccordingToNSPR();
245 *result = cnt;
246 return NS_OK;
249 nsresult nsFileStreamBase::WriteFrom(nsIInputStream* inStr, uint32_t count,
250 uint32_t* _retval) {
251 MOZ_ASSERT_UNREACHABLE("WriteFrom (see source comment)");
252 return NS_ERROR_NOT_IMPLEMENTED;
253 // File streams intentionally do not support this method.
254 // If you need something like this, then you should wrap
255 // the file stream using nsIBufferedOutputStream
258 nsresult nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void* closure,
259 uint32_t count, uint32_t* _retval) {
260 return NS_ERROR_NOT_IMPLEMENTED;
261 // File streams intentionally do not support this method.
262 // If you need something like this, then you should wrap
263 // the file stream using nsIBufferedOutputStream
266 nsresult nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags,
267 int32_t aPerm, bool aDeferred) {
268 NS_ENSURE_STATE(aFile);
270 mOpenParams.ioFlags = aIoFlags;
271 mOpenParams.perm = aPerm;
273 if (aDeferred) {
274 // Clone the file, as it may change between now and the deferred open
275 nsCOMPtr<nsIFile> file;
276 nsresult rv = aFile->Clone(getter_AddRefs(file));
277 NS_ENSURE_SUCCESS(rv, rv);
279 mOpenParams.localFile = std::move(file);
280 NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED);
282 mState = eDeferredOpen;
283 return NS_OK;
286 mOpenParams.localFile = aFile;
288 // Following call open() at main thread.
289 // Main thread might be blocked, while open a remote file.
290 return DoOpen();
293 void nsFileStreamBase::CleanUpOpen() { mOpenParams.localFile = nullptr; }
295 nsresult nsFileStreamBase::DoOpen() {
296 MOZ_ASSERT(mState == eDeferredOpen || mState == eUnitialized ||
297 mState == eClosed);
298 NS_ASSERTION(!mFD, "Already have a file descriptor!");
299 NS_ASSERTION(mOpenParams.localFile, "Must have a file to open");
301 PRFileDesc* fd;
302 nsresult rv;
304 if (mOpenParams.ioFlags & PR_CREATE_FILE) {
305 nsCOMPtr<nsIFile> parent;
306 mOpenParams.localFile->GetParent(getter_AddRefs(parent));
308 // Result doesn't need to be checked. If the file's parent path does not
309 // exist, make it. If it does exist, do nothing.
310 if (parent) {
311 Unused << parent->Create(nsIFile::DIRECTORY_TYPE, 0755);
315 #ifdef XP_WIN
316 if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) {
317 nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(mOpenParams.localFile);
318 MOZ_ASSERT(file);
320 rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags,
321 mOpenParams.perm, &fd);
322 } else
323 #endif // XP_WIN
325 rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags,
326 mOpenParams.perm, &fd);
329 if (rv == NS_OK && IOActivityMonitor::IsActive()) {
330 auto nativePath = mOpenParams.localFile->NativePath();
331 if (!nativePath.IsEmpty()) {
332 // registering the file to the activity monitor
333 #ifdef XP_WIN
334 // 16 bits unicode
335 IOActivityMonitor::MonitorFile(
336 fd, NS_ConvertUTF16toUTF8(nativePath.get()).get());
337 #else
338 // 8 bit unicode
339 IOActivityMonitor::MonitorFile(fd, nativePath.get());
340 #endif
344 CleanUpOpen();
346 if (NS_FAILED(rv)) {
347 mState = eError;
348 mErrorValue = rv;
349 return rv;
352 mFD = fd;
353 mState = eOpened;
355 return NS_OK;
358 nsresult nsFileStreamBase::DoPendingOpen() {
359 switch (mState) {
360 case eUnitialized:
361 MOZ_CRASH("This should not happen.");
362 return NS_ERROR_FAILURE;
364 case eDeferredOpen:
365 return DoOpen();
367 case eOpened:
368 MOZ_ASSERT(mFD);
369 if (NS_WARN_IF(!mFD)) {
370 return NS_ERROR_FAILURE;
372 return NS_OK;
374 case eClosed:
375 MOZ_ASSERT(!mFD);
376 return NS_BASE_STREAM_CLOSED;
378 case eError:
379 return mErrorValue;
382 MOZ_CRASH("Invalid mState value.");
383 return NS_ERROR_FAILURE;
386 ////////////////////////////////////////////////////////////////////////////////
387 // nsFileInputStream
389 NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase)
390 NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase)
392 NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE,
393 NS_LOCALFILEINPUTSTREAM_CID)
395 NS_INTERFACE_MAP_BEGIN(nsFileInputStream)
396 NS_INTERFACE_MAP_ENTRY(nsIInputStream)
397 NS_INTERFACE_MAP_ENTRY(nsIFileInputStream)
398 NS_INTERFACE_MAP_ENTRY(nsILineInputStream)
399 NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
400 NS_IMPL_QUERY_CLASSINFO(nsFileInputStream)
401 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable())
402 NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
404 NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream, nsIInputStream,
405 nsIFileInputStream, nsISeekableStream,
406 nsITellableStream, nsILineInputStream)
408 nsresult nsFileInputStream::Create(REFNSIID aIID, void** aResult) {
409 RefPtr<nsFileInputStream> stream = new nsFileInputStream();
410 return stream->QueryInterface(aIID, aResult);
413 nsresult nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags,
414 int32_t aPerm) {
415 nsresult rv = NS_OK;
417 // If the previous file is open, close it
418 if (mFD) {
419 rv = Close();
420 if (NS_FAILED(rv)) return rv;
423 // Open the file
424 if (aIOFlags == -1) aIOFlags = PR_RDONLY;
425 if (aPerm == -1) aPerm = 0;
427 return MaybeOpen(aFile, aIOFlags, aPerm,
428 mBehaviorFlags & nsIFileInputStream::DEFER_OPEN);
431 NS_IMETHODIMP
432 nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
433 int32_t aBehaviorFlags) {
434 NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED);
435 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
436 NS_ERROR_ALREADY_INITIALIZED);
438 mBehaviorFlags = aBehaviorFlags;
439 mState = eUnitialized;
441 mFile = aFile;
442 mIOFlags = aIOFlags;
443 mPerm = aPerm;
445 return Open(aFile, aIOFlags, aPerm);
448 NS_IMETHODIMP
449 nsFileInputStream::Close() {
450 // Get the cache position at the time the file was close. This allows
451 // NS_SEEK_CUR on a closed file that has been opened with
452 // REOPEN_ON_REWIND.
453 if (mBehaviorFlags & REOPEN_ON_REWIND) {
454 // Get actual position. Not one modified by subclasses
455 nsFileStreamBase::Tell(&mCachedPosition);
458 // null out mLineBuffer in case Close() is called again after failing
459 mLineBuffer = nullptr;
460 return nsFileStreamBase::Close();
463 NS_IMETHODIMP
464 nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
465 nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval);
466 if (rv == NS_ERROR_FILE_NOT_FOUND) {
467 // Don't warn if this is a deffered file not found.
468 return rv;
471 NS_ENSURE_SUCCESS(rv, rv);
473 // Check if we're at the end of file and need to close
474 if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) {
475 Close();
478 return NS_OK;
481 NS_IMETHODIMP
482 nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult) {
483 if (!mLineBuffer) {
484 mLineBuffer = MakeUnique<nsLineBuffer<char>>();
486 return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult);
489 NS_IMETHODIMP
490 nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset) {
491 return SeekInternal(aWhence, aOffset);
494 nsresult nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset,
495 bool aClearBuf) {
496 nsresult rv = DoPendingOpen();
497 if (rv != NS_OK && rv != NS_BASE_STREAM_CLOSED) {
498 return rv;
501 if (aClearBuf) {
502 mLineBuffer = nullptr;
505 if (rv == NS_BASE_STREAM_CLOSED) {
506 if (mBehaviorFlags & REOPEN_ON_REWIND) {
507 rv = Open(mFile, mIOFlags, mPerm);
508 NS_ENSURE_SUCCESS(rv, rv);
510 // If the file was closed, and we do a relative seek, use the
511 // position we cached when we closed the file to seek to the right
512 // location.
513 if (aWhence == NS_SEEK_CUR) {
514 aWhence = NS_SEEK_SET;
515 aOffset += mCachedPosition;
517 // If we're trying to seek to the start then we're done, so
518 // return early to avoid Seek from calling DoPendingOpen and
519 // opening the underlying file earlier than necessary.
520 if (aWhence == NS_SEEK_SET && aOffset == 0) {
521 return NS_OK;
523 } else {
524 return NS_BASE_STREAM_CLOSED;
528 return nsFileStreamBase::Seek(aWhence, aOffset);
531 NS_IMETHODIMP
532 nsFileInputStream::Tell(int64_t* aResult) {
533 return nsFileStreamBase::Tell(aResult);
536 NS_IMETHODIMP
537 nsFileInputStream::Available(uint64_t* aResult) {
538 return nsFileStreamBase::Available(aResult);
541 void nsFileInputStream::SerializedComplexity(uint32_t aMaxSize,
542 uint32_t* aSizeUsed,
543 uint32_t* aPipes,
544 uint32_t* aTransferables) {
545 *aTransferables = 1;
548 void nsFileInputStream::Serialize(InputStreamParams& aParams, uint32_t aMaxSize,
549 uint32_t* aSizeUsed) {
550 MOZ_ASSERT(aSizeUsed);
551 *aSizeUsed = 0;
553 FileInputStreamParams params;
555 if (NS_SUCCEEDED(DoPendingOpen())) {
556 MOZ_ASSERT(mFD);
557 FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
558 NS_ASSERTION(fd, "This should never be null!");
560 params.fileDescriptor() = FileDescriptor(fd);
562 Close();
563 } else {
564 NS_WARNING(
565 "This file has not been opened (or could not be opened). "
566 "Sending an invalid file descriptor to the other process!");
568 params.fileDescriptor() = FileDescriptor();
571 int32_t behaviorFlags = mBehaviorFlags;
573 // The receiving process (or thread) is going to have an open file
574 // descriptor automatically so transferring this flag is meaningless.
575 behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
577 params.behaviorFlags() = behaviorFlags;
578 params.ioFlags() = mIOFlags;
580 aParams = params;
583 bool nsFileInputStream::Deserialize(const InputStreamParams& aParams) {
584 NS_ASSERTION(!mFD, "Already have a file descriptor?!");
585 NS_ASSERTION(mState == nsFileStreamBase::eUnitialized, "Deferring open?!");
586 NS_ASSERTION(!mFile, "Should never have a file here!");
587 NS_ASSERTION(!mPerm, "This should always be 0!");
589 if (aParams.type() != InputStreamParams::TFileInputStreamParams) {
590 NS_WARNING("Received unknown parameters from the other process!");
591 return false;
594 const FileInputStreamParams& params = aParams.get_FileInputStreamParams();
596 const FileDescriptor& fd = params.fileDescriptor();
598 if (fd.IsValid()) {
599 auto rawFD = fd.ClonePlatformHandle();
600 PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
601 if (!fileDesc) {
602 NS_WARNING("Failed to import file handle!");
603 return false;
605 mFD = fileDesc;
606 mState = eOpened;
607 } else {
608 NS_WARNING("Received an invalid file descriptor!");
609 mState = eError;
610 mErrorValue = NS_ERROR_FILE_NOT_FOUND;
613 mBehaviorFlags = params.behaviorFlags();
615 if (!XRE_IsParentProcess()) {
616 // A child process shouldn't close when it reads the end because it will
617 // not be able to reopen the file later.
618 mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF;
620 // A child process will not be able to reopen the file so this flag is
621 // meaningless.
622 mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
625 mIOFlags = params.ioFlags();
627 return true;
630 bool nsFileInputStream::IsCloneable() const {
631 // This inputStream is cloneable only if has been created using Init() and
632 // it owns a nsIFile. This is not true when it is deserialized from IPC.
633 return XRE_IsParentProcess() && mFile;
636 NS_IMETHODIMP
637 nsFileInputStream::GetCloneable(bool* aCloneable) {
638 *aCloneable = IsCloneable();
639 return NS_OK;
642 NS_IMETHODIMP
643 nsFileInputStream::Clone(nsIInputStream** aResult) {
644 MOZ_ASSERT(IsCloneable());
645 return NS_NewLocalFileInputStream(aResult, mFile, mIOFlags, mPerm,
646 mBehaviorFlags);
649 ////////////////////////////////////////////////////////////////////////////////
650 // nsFileOutputStream
652 NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream, nsFileStreamBase,
653 nsIOutputStream, nsIFileOutputStream)
655 nsresult nsFileOutputStream::Create(REFNSIID aIID, void** aResult) {
656 RefPtr<nsFileOutputStream> stream = new nsFileOutputStream();
657 return stream->QueryInterface(aIID, aResult);
660 NS_IMETHODIMP
661 nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
662 int32_t behaviorFlags) {
663 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
664 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
665 NS_ERROR_ALREADY_INITIALIZED);
667 mBehaviorFlags = behaviorFlags;
668 mState = eUnitialized;
670 if (ioFlags == -1) ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
671 if (perm <= 0) perm = 0664;
673 return MaybeOpen(file, ioFlags, perm,
674 mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN);
677 nsresult nsFileOutputStream::InitWithFileDescriptor(
678 const mozilla::ipc::FileDescriptor& aFd) {
679 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
680 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
681 NS_ERROR_ALREADY_INITIALIZED);
683 if (aFd.IsValid()) {
684 auto rawFD = aFd.ClonePlatformHandle();
685 PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
686 if (!fileDesc) {
687 NS_WARNING("Failed to import file handle!");
688 return NS_ERROR_FAILURE;
690 mFD = fileDesc;
691 mState = eOpened;
692 } else {
693 mState = eError;
694 mErrorValue = NS_ERROR_FILE_NOT_FOUND;
697 return NS_OK;
700 NS_IMETHODIMP
701 nsFileOutputStream::Preallocate(int64_t aLength) {
702 if (!mFD) {
703 return NS_ERROR_NOT_INITIALIZED;
706 if (!mozilla::fallocate(mFD, aLength)) {
707 return NS_ERROR_FAILURE;
710 return NS_OK;
713 ////////////////////////////////////////////////////////////////////////////////
714 // nsAtomicFileOutputStream
716 NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream, nsFileOutputStream,
717 nsISafeOutputStream, nsIOutputStream,
718 nsIFileOutputStream)
720 NS_IMETHODIMP
721 nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
722 int32_t behaviorFlags) {
723 // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter
724 // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending
725 // to existing file. So, throw an exception only if `PR_APPEND` is
726 // explicitly specified without `PR_TRUNCATE`.
727 if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) {
728 return NS_ERROR_INVALID_ARG;
730 return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags);
733 nsresult nsAtomicFileOutputStream::DoOpen() {
734 // Make sure mOpenParams.localFile will be empty if we bail somewhere in
735 // this function
736 nsCOMPtr<nsIFile> file;
737 file.swap(mOpenParams.localFile);
739 if (!file) {
740 return NS_ERROR_NOT_INITIALIZED;
742 nsresult rv = file->Exists(&mTargetFileExists);
743 if (NS_FAILED(rv)) {
744 NS_ERROR("Can't tell if target file exists");
745 mTargetFileExists =
746 true; // Safer to assume it exists - we just do more work.
749 // follow symlinks, for two reasons:
750 // 1) if a user has deliberately set up a profile file as a symlink, we
751 // honor it
752 // 2) to make the MoveToNative() in Finish() an atomic operation (which may
753 // not be the case if moving across directories on different
754 // filesystems).
755 nsCOMPtr<nsIFile> tempResult;
756 rv = file->Clone(getter_AddRefs(tempResult));
757 if (NS_SUCCEEDED(rv) && mTargetFileExists) {
758 tempResult->Normalize();
761 if (NS_SUCCEEDED(rv) && mTargetFileExists) {
762 // Abort if |file| is not writable; it won't work as an output stream.
763 bool isWritable;
764 if (NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) {
765 return NS_ERROR_FILE_ACCESS_DENIED;
768 uint32_t origPerm;
769 if (NS_FAILED(file->GetPermissions(&origPerm))) {
770 NS_ERROR("Can't get permissions of target file");
771 origPerm = mOpenParams.perm;
774 // XXX What if |perm| is more restrictive then |origPerm|?
775 // This leaves the user supplied permissions as they were.
776 rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm);
778 if (NS_SUCCEEDED(rv)) {
779 // nsFileOutputStream::DoOpen will work on the temporary file, so we
780 // prepare it and place it in mOpenParams.localFile.
781 mOpenParams.localFile = tempResult;
782 mTempFile = tempResult;
783 mTargetFile = file;
784 rv = nsFileOutputStream::DoOpen();
786 return rv;
789 NS_IMETHODIMP
790 nsAtomicFileOutputStream::Close() {
791 nsresult rv = nsFileOutputStream::Close();
793 // the consumer doesn't want the original file overwritten -
794 // so clean up by removing the temp file.
795 if (mTempFile) {
796 mTempFile->Remove(false);
797 mTempFile = nullptr;
800 return rv;
803 NS_IMETHODIMP
804 nsAtomicFileOutputStream::Finish() {
805 nsresult rv = nsFileOutputStream::Close();
807 // if there is no temp file, don't try to move it over the original target.
808 // It would destroy the targetfile if close() is called twice.
809 if (!mTempFile) return rv;
811 // Only overwrite if everything was ok, and the temp file could be closed.
812 if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) {
813 NS_ENSURE_STATE(mTargetFile);
815 if (!mTargetFileExists) {
816 // If the target file did not exist when we were initialized, then the
817 // temp file we gave out was actually a reference to the target file.
818 // since we succeeded in writing to the temp file (and hence succeeded
819 // in writing to the target file), there is nothing more to do.
820 #ifdef DEBUG
821 bool equal;
822 if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal) {
823 NS_WARNING("mTempFile not equal to mTargetFile");
825 #endif
826 } else {
827 nsAutoString targetFilename;
828 rv = mTargetFile->GetLeafName(targetFilename);
829 if (NS_SUCCEEDED(rv)) {
830 // This will replace target.
831 rv = mTempFile->MoveTo(nullptr, targetFilename);
832 if (NS_FAILED(rv)) mTempFile->Remove(false);
835 } else {
836 mTempFile->Remove(false);
838 // if writing failed, propagate the failure code to the caller.
839 if (NS_FAILED(mWriteResult)) rv = mWriteResult;
841 mTempFile = nullptr;
842 return rv;
845 NS_IMETHODIMP
846 nsAtomicFileOutputStream::Write(const char* buf, uint32_t count,
847 uint32_t* result) {
848 nsresult rv = nsFileOutputStream::Write(buf, count, result);
849 if (NS_SUCCEEDED(mWriteResult)) {
850 if (NS_FAILED(rv)) {
851 mWriteResult = rv;
852 } else if (count != *result) {
853 mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
856 if (NS_FAILED(mWriteResult) && count > 0) {
857 NS_WARNING("writing to output stream failed! data may be lost");
860 return rv;
863 ////////////////////////////////////////////////////////////////////////////////
864 // nsSafeFileOutputStream
866 NS_IMETHODIMP
867 nsSafeFileOutputStream::Finish() {
868 (void)Flush();
869 return nsAtomicFileOutputStream::Finish();
872 ////////////////////////////////////////////////////////////////////////////////
873 // nsFileRandomAccessStream
875 nsresult nsFileRandomAccessStream::Create(REFNSIID aIID, void** aResult) {
876 RefPtr<nsFileRandomAccessStream> stream = new nsFileRandomAccessStream();
877 return stream->QueryInterface(aIID, aResult);
880 NS_IMPL_ISUPPORTS_INHERITED(nsFileRandomAccessStream, nsFileStreamBase,
881 nsIRandomAccessStream, nsIFileRandomAccessStream,
882 nsIInputStream, nsIOutputStream)
884 NS_IMETHODIMP
885 nsFileRandomAccessStream::GetInputStream(nsIInputStream** aInputStream) {
886 nsCOMPtr<nsIInputStream> inputStream(this);
888 inputStream.forget(aInputStream);
889 return NS_OK;
892 NS_IMETHODIMP
893 nsFileRandomAccessStream::GetOutputStream(nsIOutputStream** aOutputStream) {
894 nsCOMPtr<nsIOutputStream> outputStream(this);
896 outputStream.forget(aOutputStream);
897 return NS_OK;
900 nsIInputStream* nsFileRandomAccessStream::InputStream() { return this; }
902 nsIOutputStream* nsFileRandomAccessStream::OutputStream() { return this; }
904 RandomAccessStreamParams nsFileRandomAccessStream::Serialize() {
905 FileRandomAccessStreamParams params;
907 if (NS_SUCCEEDED(DoPendingOpen())) {
908 MOZ_ASSERT(mFD);
909 FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
910 MOZ_ASSERT(fd, "This should never be null!");
912 params.fileDescriptor() = FileDescriptor(fd);
914 Close();
915 } else {
916 NS_WARNING(
917 "This file has not been opened (or could not be opened). "
918 "Sending an invalid file descriptor to the other process!");
920 params.fileDescriptor() = FileDescriptor();
923 int32_t behaviorFlags = mBehaviorFlags;
925 // The receiving process (or thread) is going to have an open file
926 // descriptor automatically so transferring this flag is meaningless.
927 behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
929 params.behaviorFlags() = behaviorFlags;
931 return params;
934 bool nsFileRandomAccessStream::Deserialize(
935 RandomAccessStreamParams& aStreamParams) {
936 MOZ_ASSERT(!mFD, "Already have a file descriptor?!");
937 MOZ_ASSERT(mState == nsFileStreamBase::eUnitialized, "Deferring open?!");
939 if (aStreamParams.type() !=
940 RandomAccessStreamParams::TFileRandomAccessStreamParams) {
941 NS_WARNING("Received unknown parameters from the other process!");
942 return false;
945 const FileRandomAccessStreamParams& params =
946 aStreamParams.get_FileRandomAccessStreamParams();
948 const FileDescriptor& fd = params.fileDescriptor();
950 if (fd.IsValid()) {
951 auto rawFD = fd.ClonePlatformHandle();
952 PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
953 if (!fileDesc) {
954 NS_WARNING("Failed to import file handle!");
955 return false;
957 mFD = fileDesc;
958 mState = eOpened;
959 } else {
960 NS_WARNING("Received an invalid file descriptor!");
961 mState = eError;
962 mErrorValue = NS_ERROR_FILE_NOT_FOUND;
965 mBehaviorFlags = params.behaviorFlags();
967 return true;
970 NS_IMETHODIMP
971 nsFileRandomAccessStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
972 int32_t behaviorFlags) {
973 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
974 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
975 NS_ERROR_ALREADY_INITIALIZED);
977 mBehaviorFlags = behaviorFlags;
978 mState = eUnitialized;
980 if (ioFlags == -1) ioFlags = PR_RDWR;
981 if (perm <= 0) perm = 0;
983 return MaybeOpen(file, ioFlags, perm,
984 mBehaviorFlags & nsIFileRandomAccessStream::DEFER_OPEN);
987 ////////////////////////////////////////////////////////////////////////////////