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