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/. */
5
6 #include "ipc/IPCMessageUtils.h"
7
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
16
17 #include "private/pprio.h"
18 #include "prerror.h"
19
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/Unused.h"
29 #include "mozilla/FileUtils.h"
30 #include "nsNetCID.h"
31 #include "nsXULAppAPI.h"
32
33 using FileHandleType = mozilla::ipc::FileDescriptor::PlatformHandleType;
34
35 using namespace mozilla::ipc;
36 using namespace mozilla::net;
37
38 using mozilla::DebugOnly;
39 using mozilla::Maybe;
40 using mozilla::Nothing;
41 using mozilla::Some;
42
43 ////////////////////////////////////////////////////////////////////////////////
44 // nsFileStreamBase
45
~nsFileStreamBase()46 nsFileStreamBase::~nsFileStreamBase() {
47 // We don't want to try to rewrind the stream when shutting down.
48 mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
49
50 Close();
51 }
52
NS_IMPL_ISUPPORTS(nsFileStreamBase,nsISeekableStream,nsITellableStream,nsIFileMetadata)53 NS_IMPL_ISUPPORTS(nsFileStreamBase, nsISeekableStream, nsITellableStream,
54 nsIFileMetadata)
55
56 NS_IMETHODIMP
57 nsFileStreamBase::Seek(int32_t whence, int64_t offset) {
58 nsresult rv = DoPendingOpen();
59 NS_ENSURE_SUCCESS(rv, rv);
60
61 int64_t cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence);
62 if (cnt == int64_t(-1)) {
63 return NS_ErrorAccordingToNSPR();
64 }
65 return NS_OK;
66 }
67
68 NS_IMETHODIMP
Tell(int64_t * result)69 nsFileStreamBase::Tell(int64_t* result) {
70 if (mState == eDeferredOpen && !(mOpenParams.ioFlags & PR_APPEND)) {
71 *result = 0;
72 return NS_OK;
73 }
74
75 nsresult rv = DoPendingOpen();
76 NS_ENSURE_SUCCESS(rv, rv);
77
78 int64_t cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR);
79 if (cnt == int64_t(-1)) {
80 return NS_ErrorAccordingToNSPR();
81 }
82 *result = cnt;
83 return NS_OK;
84 }
85
86 NS_IMETHODIMP
SetEOF()87 nsFileStreamBase::SetEOF() {
88 nsresult rv = DoPendingOpen();
89 NS_ENSURE_SUCCESS(rv, rv);
90
91 #if defined(XP_UNIX)
92 // Some system calls require an EOF offset.
93 int64_t offset;
94 rv = Tell(&offset);
95 if (NS_FAILED(rv)) return rv;
96 #endif
97
98 #if defined(XP_UNIX)
99 if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
100 NS_ERROR("ftruncate failed");
101 return NS_ERROR_FAILURE;
102 }
103 #elif defined(XP_WIN)
104 if (!SetEndOfFile((HANDLE)PR_FileDesc2NativeHandle(mFD))) {
105 NS_ERROR("SetEndOfFile failed");
106 return NS_ERROR_FAILURE;
107 }
108 #else
109 // XXX not implemented
110 #endif
111
112 return NS_OK;
113 }
114
115 NS_IMETHODIMP
GetSize(int64_t * _retval)116 nsFileStreamBase::GetSize(int64_t* _retval) {
117 nsresult rv = DoPendingOpen();
118 NS_ENSURE_SUCCESS(rv, rv);
119
120 PRFileInfo64 info;
121 if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
122 return NS_BASE_STREAM_OSERROR;
123 }
124
125 *_retval = int64_t(info.size);
126
127 return NS_OK;
128 }
129
130 NS_IMETHODIMP
GetLastModified(int64_t * _retval)131 nsFileStreamBase::GetLastModified(int64_t* _retval) {
132 nsresult rv = DoPendingOpen();
133 NS_ENSURE_SUCCESS(rv, rv);
134
135 PRFileInfo64 info;
136 if (PR_GetOpenFileInfo64(mFD, &info) == PR_FAILURE) {
137 return NS_BASE_STREAM_OSERROR;
138 }
139
140 int64_t modTime = int64_t(info.modifyTime);
141 if (modTime == 0) {
142 *_retval = 0;
143 } else {
144 *_retval = modTime / int64_t(PR_USEC_PER_MSEC);
145 }
146
147 return NS_OK;
148 }
149
150 NS_IMETHODIMP
GetFileDescriptor(PRFileDesc ** _retval)151 nsFileStreamBase::GetFileDescriptor(PRFileDesc** _retval) {
152 nsresult rv = DoPendingOpen();
153 NS_ENSURE_SUCCESS(rv, rv);
154
155 *_retval = mFD;
156 return NS_OK;
157 }
158
Close()159 nsresult nsFileStreamBase::Close() {
160 CleanUpOpen();
161
162 nsresult rv = NS_OK;
163 if (mFD) {
164 if (PR_Close(mFD) == PR_FAILURE) rv = NS_BASE_STREAM_OSERROR;
165 mFD = nullptr;
166 mState = eClosed;
167 }
168 return rv;
169 }
170
Available(uint64_t * aResult)171 nsresult nsFileStreamBase::Available(uint64_t* aResult) {
172 nsresult rv = DoPendingOpen();
173 NS_ENSURE_SUCCESS(rv, rv);
174
175 // PR_Available with files over 4GB returns an error, so we have to
176 // use the 64-bit version of PR_Available.
177 int64_t avail = PR_Available64(mFD);
178 if (avail == -1) {
179 return NS_ErrorAccordingToNSPR();
180 }
181
182 // If available is greater than 4GB, return 4GB
183 *aResult = (uint64_t)avail;
184 return NS_OK;
185 }
186
Read(char * aBuf,uint32_t aCount,uint32_t * aResult)187 nsresult nsFileStreamBase::Read(char* aBuf, uint32_t aCount,
188 uint32_t* aResult) {
189 nsresult rv = DoPendingOpen();
190 if (rv == NS_BASE_STREAM_CLOSED) {
191 *aResult = 0;
192 return NS_OK;
193 }
194
195 if (NS_FAILED(rv)) {
196 return rv;
197 }
198
199 int32_t bytesRead = PR_Read(mFD, aBuf, aCount);
200 if (bytesRead == -1) {
201 return NS_ErrorAccordingToNSPR();
202 }
203
204 *aResult = bytesRead;
205 return NS_OK;
206 }
207
ReadSegments(nsWriteSegmentFun aWriter,void * aClosure,uint32_t aCount,uint32_t * aResult)208 nsresult nsFileStreamBase::ReadSegments(nsWriteSegmentFun aWriter,
209 void* aClosure, uint32_t aCount,
210 uint32_t* aResult) {
211 // ReadSegments is not implemented because it would be inefficient when
212 // the writer does not consume all data. If you want to call ReadSegments,
213 // wrap a BufferedInputStream around the file stream. That will call
214 // Read().
215
216 return NS_ERROR_NOT_IMPLEMENTED;
217 }
218
IsNonBlocking(bool * aNonBlocking)219 nsresult nsFileStreamBase::IsNonBlocking(bool* aNonBlocking) {
220 *aNonBlocking = false;
221 return NS_OK;
222 }
223
Flush(void)224 nsresult nsFileStreamBase::Flush(void) {
225 nsresult rv = DoPendingOpen();
226 NS_ENSURE_SUCCESS(rv, rv);
227
228 int32_t cnt = PR_Sync(mFD);
229 if (cnt == -1) {
230 return NS_ErrorAccordingToNSPR();
231 }
232 return NS_OK;
233 }
234
Write(const char * buf,uint32_t count,uint32_t * result)235 nsresult nsFileStreamBase::Write(const char* buf, uint32_t count,
236 uint32_t* result) {
237 nsresult rv = DoPendingOpen();
238 NS_ENSURE_SUCCESS(rv, rv);
239
240 int32_t cnt = PR_Write(mFD, buf, count);
241 if (cnt == -1) {
242 return NS_ErrorAccordingToNSPR();
243 }
244 *result = cnt;
245 return NS_OK;
246 }
247
WriteFrom(nsIInputStream * inStr,uint32_t count,uint32_t * _retval)248 nsresult nsFileStreamBase::WriteFrom(nsIInputStream* inStr, uint32_t count,
249 uint32_t* _retval) {
250 MOZ_ASSERT_UNREACHABLE("WriteFrom (see source comment)");
251 return NS_ERROR_NOT_IMPLEMENTED;
252 // File streams intentionally do not support this method.
253 // If you need something like this, then you should wrap
254 // the file stream using nsIBufferedOutputStream
255 }
256
WriteSegments(nsReadSegmentFun reader,void * closure,uint32_t count,uint32_t * _retval)257 nsresult nsFileStreamBase::WriteSegments(nsReadSegmentFun reader, void* closure,
258 uint32_t count, uint32_t* _retval) {
259 return NS_ERROR_NOT_IMPLEMENTED;
260 // File streams intentionally do not support this method.
261 // If you need something like this, then you should wrap
262 // the file stream using nsIBufferedOutputStream
263 }
264
MaybeOpen(nsIFile * aFile,int32_t aIoFlags,int32_t aPerm,bool aDeferred)265 nsresult nsFileStreamBase::MaybeOpen(nsIFile* aFile, int32_t aIoFlags,
266 int32_t aPerm, bool aDeferred) {
267 NS_ENSURE_STATE(aFile);
268
269 mOpenParams.ioFlags = aIoFlags;
270 mOpenParams.perm = aPerm;
271
272 if (aDeferred) {
273 // Clone the file, as it may change between now and the deferred open
274 nsCOMPtr<nsIFile> file;
275 nsresult rv = aFile->Clone(getter_AddRefs(file));
276 NS_ENSURE_SUCCESS(rv, rv);
277
278 mOpenParams.localFile = std::move(file);
279 NS_ENSURE_TRUE(mOpenParams.localFile, NS_ERROR_UNEXPECTED);
280
281 mState = eDeferredOpen;
282 return NS_OK;
283 }
284
285 mOpenParams.localFile = aFile;
286
287 // Following call open() at main thread.
288 // Main thread might be blocked, while open a remote file.
289 return DoOpen();
290 }
291
CleanUpOpen()292 void nsFileStreamBase::CleanUpOpen() { mOpenParams.localFile = nullptr; }
293
DoOpen()294 nsresult nsFileStreamBase::DoOpen() {
295 MOZ_ASSERT(mState == eDeferredOpen || mState == eUnitialized ||
296 mState == eClosed);
297 NS_ASSERTION(!mFD, "Already have a file descriptor!");
298 NS_ASSERTION(mOpenParams.localFile, "Must have a file to open");
299
300 PRFileDesc* fd;
301 nsresult rv;
302
303 if (mOpenParams.ioFlags & PR_CREATE_FILE) {
304 nsCOMPtr<nsIFile> parent;
305 mOpenParams.localFile->GetParent(getter_AddRefs(parent));
306
307 // Result doesn't need to be checked. If the file's parent path does not
308 // exist, make it. If it does exist, do nothing.
309 if (parent) {
310 Unused << parent->Create(nsIFile::DIRECTORY_TYPE, 0755);
311 }
312 }
313
314 #ifdef XP_WIN
315 if (mBehaviorFlags & nsIFileInputStream::SHARE_DELETE) {
316 nsCOMPtr<nsILocalFileWin> file = do_QueryInterface(mOpenParams.localFile);
317 MOZ_ASSERT(file);
318
319 rv = file->OpenNSPRFileDescShareDelete(mOpenParams.ioFlags,
320 mOpenParams.perm, &fd);
321 } else
322 #endif // XP_WIN
323 {
324 rv = mOpenParams.localFile->OpenNSPRFileDesc(mOpenParams.ioFlags,
325 mOpenParams.perm, &fd);
326 }
327
328 if (rv == NS_OK && IOActivityMonitor::IsActive()) {
329 auto nativePath = mOpenParams.localFile->NativePath();
330 if (!nativePath.IsEmpty()) {
331 // registering the file to the activity monitor
332 #ifdef XP_WIN
333 // 16 bits unicode
334 IOActivityMonitor::MonitorFile(
335 fd, NS_ConvertUTF16toUTF8(nativePath.get()).get());
336 #else
337 // 8 bit unicode
338 IOActivityMonitor::MonitorFile(fd, nativePath.get());
339 #endif
340 }
341 }
342
343 CleanUpOpen();
344
345 if (NS_FAILED(rv)) {
346 mState = eError;
347 mErrorValue = rv;
348 return rv;
349 }
350
351 mFD = fd;
352 mState = eOpened;
353
354 return NS_OK;
355 }
356
DoPendingOpen()357 nsresult nsFileStreamBase::DoPendingOpen() {
358 switch (mState) {
359 case eUnitialized:
360 MOZ_CRASH("This should not happen.");
361 return NS_ERROR_FAILURE;
362
363 case eDeferredOpen:
364 return DoOpen();
365
366 case eOpened:
367 MOZ_ASSERT(mFD);
368 if (NS_WARN_IF(!mFD)) {
369 return NS_ERROR_FAILURE;
370 }
371 return NS_OK;
372
373 case eClosed:
374 MOZ_ASSERT(!mFD);
375 return NS_BASE_STREAM_CLOSED;
376
377 case eError:
378 return mErrorValue;
379 }
380
381 MOZ_CRASH("Invalid mState value.");
382 return NS_ERROR_FAILURE;
383 }
384
385 ////////////////////////////////////////////////////////////////////////////////
386 // nsFileInputStream
387
NS_IMPL_ADDREF_INHERITED(nsFileInputStream,nsFileStreamBase)388 NS_IMPL_ADDREF_INHERITED(nsFileInputStream, nsFileStreamBase)
389 NS_IMPL_RELEASE_INHERITED(nsFileInputStream, nsFileStreamBase)
390
391 NS_IMPL_CLASSINFO(nsFileInputStream, nullptr, nsIClassInfo::THREADSAFE,
392 NS_LOCALFILEINPUTSTREAM_CID)
393
394 NS_INTERFACE_MAP_BEGIN(nsFileInputStream)
395 NS_INTERFACE_MAP_ENTRY(nsIInputStream)
396 NS_INTERFACE_MAP_ENTRY(nsIFileInputStream)
397 NS_INTERFACE_MAP_ENTRY(nsILineInputStream)
398 NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream)
399 NS_IMPL_QUERY_CLASSINFO(nsFileInputStream)
400 NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, IsCloneable())
401 NS_INTERFACE_MAP_END_INHERITING(nsFileStreamBase)
402
403 NS_IMPL_CI_INTERFACE_GETTER(nsFileInputStream, nsIInputStream,
404 nsIFileInputStream, nsISeekableStream,
405 nsITellableStream, nsILineInputStream)
406
407 nsresult nsFileInputStream::Create(nsISupports* aOuter, REFNSIID aIID,
408 void** aResult) {
409 NS_ENSURE_NO_AGGREGATION(aOuter);
410
411 RefPtr<nsFileInputStream> stream = new nsFileInputStream();
412 return stream->QueryInterface(aIID, aResult);
413 }
414
Open(nsIFile * aFile,int32_t aIOFlags,int32_t aPerm)415 nsresult nsFileInputStream::Open(nsIFile* aFile, int32_t aIOFlags,
416 int32_t aPerm) {
417 nsresult rv = NS_OK;
418
419 // If the previous file is open, close it
420 if (mFD) {
421 rv = Close();
422 if (NS_FAILED(rv)) return rv;
423 }
424
425 // Open the file
426 if (aIOFlags == -1) aIOFlags = PR_RDONLY;
427 if (aPerm == -1) aPerm = 0;
428
429 return MaybeOpen(aFile, aIOFlags, aPerm,
430 mBehaviorFlags & nsIFileInputStream::DEFER_OPEN);
431 }
432
433 NS_IMETHODIMP
Init(nsIFile * aFile,int32_t aIOFlags,int32_t aPerm,int32_t aBehaviorFlags)434 nsFileInputStream::Init(nsIFile* aFile, int32_t aIOFlags, int32_t aPerm,
435 int32_t aBehaviorFlags) {
436 NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED);
437 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
438 NS_ERROR_ALREADY_INITIALIZED);
439
440 mBehaviorFlags = aBehaviorFlags;
441 mState = eUnitialized;
442
443 mFile = aFile;
444 mIOFlags = aIOFlags;
445 mPerm = aPerm;
446
447 return Open(aFile, aIOFlags, aPerm);
448 }
449
450 NS_IMETHODIMP
Close()451 nsFileInputStream::Close() {
452 // Get the cache position at the time the file was close. This allows
453 // NS_SEEK_CUR on a closed file that has been opened with
454 // REOPEN_ON_REWIND.
455 if (mBehaviorFlags & REOPEN_ON_REWIND) {
456 // Get actual position. Not one modified by subclasses
457 nsFileStreamBase::Tell(&mCachedPosition);
458 }
459
460 // null out mLineBuffer in case Close() is called again after failing
461 mLineBuffer = nullptr;
462 return nsFileStreamBase::Close();
463 }
464
465 NS_IMETHODIMP
Read(char * aBuf,uint32_t aCount,uint32_t * _retval)466 nsFileInputStream::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) {
467 nsresult rv = nsFileStreamBase::Read(aBuf, aCount, _retval);
468 if (rv == NS_ERROR_FILE_NOT_FOUND) {
469 // Don't warn if this is a deffered file not found.
470 return rv;
471 }
472
473 NS_ENSURE_SUCCESS(rv, rv);
474
475 // Check if we're at the end of file and need to close
476 if (mBehaviorFlags & CLOSE_ON_EOF && *_retval == 0) {
477 Close();
478 }
479
480 return NS_OK;
481 }
482
483 NS_IMETHODIMP
ReadLine(nsACString & aLine,bool * aResult)484 nsFileInputStream::ReadLine(nsACString& aLine, bool* aResult) {
485 if (!mLineBuffer) {
486 mLineBuffer = MakeUnique<nsLineBuffer<char>>();
487 }
488 return NS_ReadLine(this, mLineBuffer.get(), aLine, aResult);
489 }
490
491 NS_IMETHODIMP
Seek(int32_t aWhence,int64_t aOffset)492 nsFileInputStream::Seek(int32_t aWhence, int64_t aOffset) {
493 return SeekInternal(aWhence, aOffset);
494 }
495
SeekInternal(int32_t aWhence,int64_t aOffset,bool aClearBuf)496 nsresult nsFileInputStream::SeekInternal(int32_t aWhence, int64_t aOffset,
497 bool aClearBuf) {
498 nsresult rv = DoPendingOpen();
499 if (rv != NS_OK && rv != NS_BASE_STREAM_CLOSED) {
500 return rv;
501 }
502
503 if (aClearBuf) {
504 mLineBuffer = nullptr;
505 }
506
507 if (rv == NS_BASE_STREAM_CLOSED) {
508 if (mBehaviorFlags & REOPEN_ON_REWIND) {
509 rv = Open(mFile, mIOFlags, mPerm);
510 NS_ENSURE_SUCCESS(rv, rv);
511
512 // If the file was closed, and we do a relative seek, use the
513 // position we cached when we closed the file to seek to the right
514 // location.
515 if (aWhence == NS_SEEK_CUR) {
516 aWhence = NS_SEEK_SET;
517 aOffset += mCachedPosition;
518 }
519 // If we're trying to seek to the start then we're done, so
520 // return early to avoid Seek from calling DoPendingOpen and
521 // opening the underlying file earlier than necessary.
522 if (aWhence == NS_SEEK_SET && aOffset == 0) {
523 return NS_OK;
524 }
525 } else {
526 return NS_BASE_STREAM_CLOSED;
527 }
528 }
529
530 return nsFileStreamBase::Seek(aWhence, aOffset);
531 }
532
533 NS_IMETHODIMP
Tell(int64_t * aResult)534 nsFileInputStream::Tell(int64_t* aResult) {
535 return nsFileStreamBase::Tell(aResult);
536 }
537
538 NS_IMETHODIMP
Available(uint64_t * aResult)539 nsFileInputStream::Available(uint64_t* aResult) {
540 return nsFileStreamBase::Available(aResult);
541 }
542
Serialize(InputStreamParams & aParams,FileDescriptorArray & aFileDescriptors,bool aDelayedStart,uint32_t aMaxSize,uint32_t * aSizeUsed,ParentToChildStreamActorManager * aManager)543 void nsFileInputStream::Serialize(InputStreamParams& aParams,
544 FileDescriptorArray& aFileDescriptors,
545 bool aDelayedStart, uint32_t aMaxSize,
546 uint32_t* aSizeUsed,
547 ParentToChildStreamActorManager* aManager) {
548 MOZ_ASSERT(aSizeUsed);
549 *aSizeUsed = 0;
550
551 SerializeInternal(aParams, aFileDescriptors);
552 }
553
Serialize(InputStreamParams & aParams,FileDescriptorArray & aFileDescriptors,bool aDelayedStart,uint32_t aMaxSize,uint32_t * aSizeUsed,ChildToParentStreamActorManager * aManager)554 void nsFileInputStream::Serialize(InputStreamParams& aParams,
555 FileDescriptorArray& aFileDescriptors,
556 bool aDelayedStart, uint32_t aMaxSize,
557 uint32_t* aSizeUsed,
558 ChildToParentStreamActorManager* aManager) {
559 MOZ_ASSERT(aSizeUsed);
560 *aSizeUsed = 0;
561
562 SerializeInternal(aParams, aFileDescriptors);
563 }
564
SerializeInternal(InputStreamParams & aParams,FileDescriptorArray & aFileDescriptors)565 void nsFileInputStream::SerializeInternal(
566 InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors) {
567 FileInputStreamParams params;
568
569 if (NS_SUCCEEDED(DoPendingOpen())) {
570 MOZ_ASSERT(mFD);
571 FileHandleType fd = FileHandleType(PR_FileDesc2NativeHandle(mFD));
572 NS_ASSERTION(fd, "This should never be null!");
573
574 DebugOnly dbgFD = aFileDescriptors.AppendElement(fd);
575 NS_ASSERTION(dbgFD->IsValid(), "Sending an invalid file descriptor!");
576
577 params.fileDescriptorIndex() = aFileDescriptors.Length() - 1;
578
579 Close();
580 } else {
581 NS_WARNING(
582 "This file has not been opened (or could not be opened). "
583 "Sending an invalid file descriptor to the other process!");
584
585 params.fileDescriptorIndex() = UINT32_MAX;
586 }
587
588 int32_t behaviorFlags = mBehaviorFlags;
589
590 // The receiving process (or thread) is going to have an open file
591 // descriptor automatically so transferring this flag is meaningless.
592 behaviorFlags &= ~nsIFileInputStream::DEFER_OPEN;
593
594 params.behaviorFlags() = behaviorFlags;
595 params.ioFlags() = mIOFlags;
596
597 aParams = params;
598 }
599
Deserialize(const InputStreamParams & aParams,const FileDescriptorArray & aFileDescriptors)600 bool nsFileInputStream::Deserialize(
601 const InputStreamParams& aParams,
602 const FileDescriptorArray& aFileDescriptors) {
603 NS_ASSERTION(!mFD, "Already have a file descriptor?!");
604 NS_ASSERTION(mState == nsFileStreamBase::eUnitialized, "Deferring open?!");
605 NS_ASSERTION(!mFile, "Should never have a file here!");
606 NS_ASSERTION(!mPerm, "This should always be 0!");
607
608 if (aParams.type() != InputStreamParams::TFileInputStreamParams) {
609 NS_WARNING("Received unknown parameters from the other process!");
610 return false;
611 }
612
613 const FileInputStreamParams& params = aParams.get_FileInputStreamParams();
614
615 uint32_t fileDescriptorIndex = params.fileDescriptorIndex();
616
617 FileDescriptor fd;
618 if (fileDescriptorIndex < aFileDescriptors.Length()) {
619 fd = aFileDescriptors[fileDescriptorIndex];
620 NS_WARNING_ASSERTION(fd.IsValid(), "Received an invalid file descriptor!");
621 } else {
622 NS_WARNING("Received a bad file descriptor index!");
623 }
624
625 if (fd.IsValid()) {
626 auto rawFD = fd.ClonePlatformHandle();
627 PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
628 if (!fileDesc) {
629 NS_WARNING("Failed to import file handle!");
630 return false;
631 }
632 mFD = fileDesc;
633 mState = eOpened;
634 } else {
635 mState = eError;
636 mErrorValue = NS_ERROR_FILE_NOT_FOUND;
637 }
638
639 mBehaviorFlags = params.behaviorFlags();
640
641 if (!XRE_IsParentProcess()) {
642 // A child process shouldn't close when it reads the end because it will
643 // not be able to reopen the file later.
644 mBehaviorFlags &= ~nsIFileInputStream::CLOSE_ON_EOF;
645
646 // A child process will not be able to reopen the file so this flag is
647 // meaningless.
648 mBehaviorFlags &= ~nsIFileInputStream::REOPEN_ON_REWIND;
649 }
650
651 mIOFlags = params.ioFlags();
652
653 return true;
654 }
655
IsCloneable() const656 bool nsFileInputStream::IsCloneable() const {
657 // This inputStream is cloneable only if has been created using Init() and
658 // it owns a nsIFile. This is not true when it is deserialized from IPC.
659 return XRE_IsParentProcess() && mFile;
660 }
661
662 NS_IMETHODIMP
GetCloneable(bool * aCloneable)663 nsFileInputStream::GetCloneable(bool* aCloneable) {
664 *aCloneable = IsCloneable();
665 return NS_OK;
666 }
667
668 NS_IMETHODIMP
Clone(nsIInputStream ** aResult)669 nsFileInputStream::Clone(nsIInputStream** aResult) {
670 MOZ_ASSERT(IsCloneable());
671 return NS_NewLocalFileInputStream(aResult, mFile, mIOFlags, mPerm,
672 mBehaviorFlags);
673 }
674
675 ////////////////////////////////////////////////////////////////////////////////
676 // nsFileOutputStream
677
NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream,nsFileStreamBase,nsIOutputStream,nsIFileOutputStream)678 NS_IMPL_ISUPPORTS_INHERITED(nsFileOutputStream, nsFileStreamBase,
679 nsIOutputStream, nsIFileOutputStream)
680
681 nsresult nsFileOutputStream::Create(nsISupports* aOuter, REFNSIID aIID,
682 void** aResult) {
683 NS_ENSURE_NO_AGGREGATION(aOuter);
684
685 RefPtr<nsFileOutputStream> stream = new nsFileOutputStream();
686 return stream->QueryInterface(aIID, aResult);
687 }
688
689 NS_IMETHODIMP
Init(nsIFile * file,int32_t ioFlags,int32_t perm,int32_t behaviorFlags)690 nsFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
691 int32_t behaviorFlags) {
692 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
693 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
694 NS_ERROR_ALREADY_INITIALIZED);
695
696 mBehaviorFlags = behaviorFlags;
697 mState = eUnitialized;
698
699 if (ioFlags == -1) ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
700 if (perm <= 0) perm = 0664;
701
702 return MaybeOpen(file, ioFlags, perm,
703 mBehaviorFlags & nsIFileOutputStream::DEFER_OPEN);
704 }
705
InitWithFileDescriptor(const mozilla::ipc::FileDescriptor & aFd)706 nsresult nsFileOutputStream::InitWithFileDescriptor(
707 const mozilla::ipc::FileDescriptor& aFd) {
708 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
709 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
710 NS_ERROR_ALREADY_INITIALIZED);
711
712 if (aFd.IsValid()) {
713 auto rawFD = aFd.ClonePlatformHandle();
714 PRFileDesc* fileDesc = PR_ImportFile(PROsfd(rawFD.release()));
715 if (!fileDesc) {
716 NS_WARNING("Failed to import file handle!");
717 return NS_ERROR_FAILURE;
718 }
719 mFD = fileDesc;
720 mState = eOpened;
721 } else {
722 mState = eError;
723 mErrorValue = NS_ERROR_FILE_NOT_FOUND;
724 }
725
726 return NS_OK;
727 }
728
729 NS_IMETHODIMP
Preallocate(int64_t aLength)730 nsFileOutputStream::Preallocate(int64_t aLength) {
731 if (!mFD) {
732 return NS_ERROR_NOT_INITIALIZED;
733 }
734
735 if (!mozilla::fallocate(mFD, aLength)) {
736 return NS_ERROR_FAILURE;
737 }
738
739 return NS_OK;
740 }
741
742 ////////////////////////////////////////////////////////////////////////////////
743 // nsAtomicFileOutputStream
744
NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream,nsFileOutputStream,nsISafeOutputStream,nsIOutputStream,nsIFileOutputStream)745 NS_IMPL_ISUPPORTS_INHERITED(nsAtomicFileOutputStream, nsFileOutputStream,
746 nsISafeOutputStream, nsIOutputStream,
747 nsIFileOutputStream)
748
749 NS_IMETHODIMP
750 nsAtomicFileOutputStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
751 int32_t behaviorFlags) {
752 // While `PR_APPEND` is not supported, `-1` is used as `ioFlags` parameter
753 // in some places, and `PR_APPEND | PR_TRUNCATE` does not require appending
754 // to existing file. So, throw an exception only if `PR_APPEND` is
755 // explicitly specified without `PR_TRUNCATE`.
756 if ((ioFlags & PR_APPEND) && !(ioFlags & PR_TRUNCATE)) {
757 return NS_ERROR_INVALID_ARG;
758 }
759 return nsFileOutputStream::Init(file, ioFlags, perm, behaviorFlags);
760 }
761
DoOpen()762 nsresult nsAtomicFileOutputStream::DoOpen() {
763 // Make sure mOpenParams.localFile will be empty if we bail somewhere in
764 // this function
765 nsCOMPtr<nsIFile> file;
766 file.swap(mOpenParams.localFile);
767
768 if (!file) {
769 return NS_ERROR_NOT_INITIALIZED;
770 }
771 nsresult rv = file->Exists(&mTargetFileExists);
772 if (NS_FAILED(rv)) {
773 NS_ERROR("Can't tell if target file exists");
774 mTargetFileExists =
775 true; // Safer to assume it exists - we just do more work.
776 }
777
778 // follow symlinks, for two reasons:
779 // 1) if a user has deliberately set up a profile file as a symlink, we
780 // honor it
781 // 2) to make the MoveToNative() in Finish() an atomic operation (which may
782 // not be the case if moving across directories on different
783 // filesystems).
784 nsCOMPtr<nsIFile> tempResult;
785 rv = file->Clone(getter_AddRefs(tempResult));
786 if (NS_SUCCEEDED(rv) && mTargetFileExists) {
787 tempResult->Normalize();
788 }
789
790 if (NS_SUCCEEDED(rv) && mTargetFileExists) {
791 // Abort if |file| is not writable; it won't work as an output stream.
792 bool isWritable;
793 if (NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) {
794 return NS_ERROR_FILE_ACCESS_DENIED;
795 }
796
797 uint32_t origPerm;
798 if (NS_FAILED(file->GetPermissions(&origPerm))) {
799 NS_ERROR("Can't get permissions of target file");
800 origPerm = mOpenParams.perm;
801 }
802
803 // XXX What if |perm| is more restrictive then |origPerm|?
804 // This leaves the user supplied permissions as they were.
805 rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm);
806 }
807 if (NS_SUCCEEDED(rv)) {
808 // nsFileOutputStream::DoOpen will work on the temporary file, so we
809 // prepare it and place it in mOpenParams.localFile.
810 mOpenParams.localFile = tempResult;
811 mTempFile = tempResult;
812 mTargetFile = file;
813 rv = nsFileOutputStream::DoOpen();
814 }
815 return rv;
816 }
817
818 NS_IMETHODIMP
Close()819 nsAtomicFileOutputStream::Close() {
820 nsresult rv = nsFileOutputStream::Close();
821
822 // the consumer doesn't want the original file overwritten -
823 // so clean up by removing the temp file.
824 if (mTempFile) {
825 mTempFile->Remove(false);
826 mTempFile = nullptr;
827 }
828
829 return rv;
830 }
831
832 NS_IMETHODIMP
Finish()833 nsAtomicFileOutputStream::Finish() {
834 nsresult rv = nsFileOutputStream::Close();
835
836 // if there is no temp file, don't try to move it over the original target.
837 // It would destroy the targetfile if close() is called twice.
838 if (!mTempFile) return rv;
839
840 // Only overwrite if everything was ok, and the temp file could be closed.
841 if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) {
842 NS_ENSURE_STATE(mTargetFile);
843
844 if (!mTargetFileExists) {
845 // If the target file did not exist when we were initialized, then the
846 // temp file we gave out was actually a reference to the target file.
847 // since we succeeded in writing to the temp file (and hence succeeded
848 // in writing to the target file), there is nothing more to do.
849 #ifdef DEBUG
850 bool equal;
851 if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal) {
852 NS_WARNING("mTempFile not equal to mTargetFile");
853 }
854 #endif
855 } else {
856 nsAutoString targetFilename;
857 rv = mTargetFile->GetLeafName(targetFilename);
858 if (NS_SUCCEEDED(rv)) {
859 // This will replace target.
860 rv = mTempFile->MoveTo(nullptr, targetFilename);
861 if (NS_FAILED(rv)) mTempFile->Remove(false);
862 }
863 }
864 } else {
865 mTempFile->Remove(false);
866
867 // if writing failed, propagate the failure code to the caller.
868 if (NS_FAILED(mWriteResult)) rv = mWriteResult;
869 }
870 mTempFile = nullptr;
871 return rv;
872 }
873
874 NS_IMETHODIMP
Write(const char * buf,uint32_t count,uint32_t * result)875 nsAtomicFileOutputStream::Write(const char* buf, uint32_t count,
876 uint32_t* result) {
877 nsresult rv = nsFileOutputStream::Write(buf, count, result);
878 if (NS_SUCCEEDED(mWriteResult)) {
879 if (NS_FAILED(rv)) {
880 mWriteResult = rv;
881 } else if (count != *result) {
882 mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
883 }
884
885 if (NS_FAILED(mWriteResult) && count > 0) {
886 NS_WARNING("writing to output stream failed! data may be lost");
887 }
888 }
889 return rv;
890 }
891
892 ////////////////////////////////////////////////////////////////////////////////
893 // nsSafeFileOutputStream
894
895 NS_IMETHODIMP
Finish()896 nsSafeFileOutputStream::Finish() {
897 (void)Flush();
898 return nsAtomicFileOutputStream::Finish();
899 }
900
901 ////////////////////////////////////////////////////////////////////////////////
902 // nsFileStream
903
Create(nsISupports * aOuter,REFNSIID aIID,void ** aResult)904 nsresult nsFileStream::Create(nsISupports* aOuter, REFNSIID aIID,
905 void** aResult) {
906 NS_ENSURE_NO_AGGREGATION(aOuter);
907
908 RefPtr<nsFileStream> stream = new nsFileStream();
909 return stream->QueryInterface(aIID, aResult);
910 }
911
NS_IMPL_ISUPPORTS_INHERITED(nsFileStream,nsFileStreamBase,nsIInputStream,nsIOutputStream,nsIFileStream)912 NS_IMPL_ISUPPORTS_INHERITED(nsFileStream, nsFileStreamBase, nsIInputStream,
913 nsIOutputStream, nsIFileStream)
914
915 NS_IMETHODIMP
916 nsFileStream::Init(nsIFile* file, int32_t ioFlags, int32_t perm,
917 int32_t behaviorFlags) {
918 NS_ENSURE_TRUE(mFD == nullptr, NS_ERROR_ALREADY_INITIALIZED);
919 NS_ENSURE_TRUE(mState == eUnitialized || mState == eClosed,
920 NS_ERROR_ALREADY_INITIALIZED);
921
922 mBehaviorFlags = behaviorFlags;
923 mState = eUnitialized;
924
925 if (ioFlags == -1) ioFlags = PR_RDWR;
926 if (perm <= 0) perm = 0;
927
928 return MaybeOpen(file, ioFlags, perm,
929 mBehaviorFlags & nsIFileStream::DEFER_OPEN);
930 }
931
932 ////////////////////////////////////////////////////////////////////////////////
933