Back to index

lightning-sunbird  0.9+nobinonly
nsFileStreams.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is mozilla.org code.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications Corporation.
00019  * Portions created by the Initial Developer are Copyright (C) 1998
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *
00024  * Alternatively, the contents of this file may be used under the terms of
00025  * either the GNU General Public License Version 2 or later (the "GPL"), or
00026  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00027  * in which case the provisions of the GPL or the LGPL are applicable instead
00028  * of those above. If you wish to allow use of your version of this file only
00029  * under the terms of either the GPL or the LGPL, and not to allow others to
00030  * use your version of this file under the terms of the MPL, indicate your
00031  * decision by deleting the provisions above and replace them with the notice
00032  * and other provisions required by the GPL or the LGPL. If you do not delete
00033  * the provisions above, a recipient may use your version of this file under
00034  * the terms of any one of the MPL, the GPL or the LGPL.
00035  *
00036  * ***** END LICENSE BLOCK ***** */
00037 
00038 #if defined(XP_UNIX) || defined(XP_BEOS)
00039 #include <unistd.h>
00040 #elif defined(XP_MAC)
00041 #include <Files.h>
00042 #elif defined(XP_WIN)
00043 #include <windows.h>
00044 #elif defined(XP_OS2)
00045 #define INCL_DOSERRORS
00046 #include <os2.h>
00047 #else
00048 // XXX add necessary include file for ftruncate (or equivalent)
00049 #endif
00050 
00051 #if defined(XP_MAC)
00052 #include "pprio.h"
00053 #else
00054 #include "private/pprio.h"
00055 #endif
00056 
00057 #include "nsFileStreams.h"
00058 #include "nsILocalFile.h"
00059 #include "nsXPIDLString.h"
00060 #include "prerror.h"
00061 #include "nsCRT.h"
00062 #include "nsInt64.h"
00063 #include "nsIFile.h"
00064 #include "nsDirectoryIndexStream.h"
00065 #include "nsMimeTypes.h"
00066 #include "nsReadLine.h"
00067 #include "nsNetUtil.h"
00068 //#include "nsFileTransportService.h"
00069 
00070 #define NS_NO_INPUT_BUFFERING 1 // see http://bugzilla.mozilla.org/show_bug.cgi?id=41067
00071 
00072 #if defined(PR_LOGGING)
00073 //
00074 // Log module for nsFileTransport logging...
00075 //
00076 // To enable logging (see prlog.h for full details):
00077 //
00078 //    set NSPR_LOG_MODULES=nsFileIO:5
00079 //    set NSPR_LOG_FILE=nspr.log
00080 //
00081 // this enables PR_LOG_DEBUG level information and places all output in
00082 // the file nspr.log
00083 //
00084 PRLogModuleInfo* gFileIOLog = nsnull;
00085 
00086 #endif /* PR_LOGGING */
00087 
00089 // nsFileStream
00090 
00091 nsFileStream::nsFileStream()
00092     : mFD(nsnull)
00093     , mCloseFD(PR_TRUE)
00094 {
00095 }
00096 
00097 nsFileStream::~nsFileStream()
00098 {
00099     if (mCloseFD)
00100         Close();
00101 }
00102 
00103 NS_IMPL_THREADSAFE_ISUPPORTS1(nsFileStream, nsISeekableStream)
00104 
00105 nsresult
00106 nsFileStream::InitWithFileDescriptor(PRFileDesc* fd, nsISupports* parent)
00107 {
00108     NS_ENSURE_TRUE(mFD == nsnull, NS_ERROR_ALREADY_INITIALIZED);
00109     //
00110     // this file stream is dependent on its parent to keep the
00111     // file descriptor valid.  an owning reference to the parent
00112     // prevents the file descriptor from going away prematurely.
00113     //
00114     mFD = fd;
00115     mCloseFD = PR_FALSE;
00116     mParent = parent;
00117     return NS_OK;
00118 }
00119 
00120 nsresult
00121 nsFileStream::Close()
00122 {
00123     nsresult rv = NS_OK;
00124     if (mFD) {
00125         if (mCloseFD)
00126             if (PR_Close(mFD) == PR_FAILURE)
00127                 rv = NS_BASE_STREAM_OSERROR;
00128         mFD = nsnull;
00129     }
00130     return rv;
00131 }
00132 
00133 NS_IMETHODIMP
00134 nsFileStream::Seek(PRInt32 whence, PRInt64 offset)
00135 {
00136     if (mFD == nsnull)
00137         return NS_BASE_STREAM_CLOSED;
00138 
00139     nsInt64 cnt = PR_Seek64(mFD, offset, (PRSeekWhence)whence);
00140     if (cnt == nsInt64(-1)) {
00141         return NS_ErrorAccordingToNSPR();
00142     }
00143     return NS_OK;
00144 }
00145 
00146 NS_IMETHODIMP
00147 nsFileStream::Tell(PRInt64 *result)
00148 {
00149     if (mFD == nsnull)
00150         return NS_BASE_STREAM_CLOSED;
00151 
00152     nsInt64 cnt = PR_Seek64(mFD, 0, PR_SEEK_CUR);
00153     if (cnt == nsInt64(-1)) {
00154         return NS_ErrorAccordingToNSPR();
00155     }
00156     *result = cnt;
00157     return NS_OK;
00158 }
00159 
00160 NS_IMETHODIMP
00161 nsFileStream::SetEOF()
00162 {
00163     if (mFD == nsnull)
00164         return NS_BASE_STREAM_CLOSED;
00165 
00166 #if defined(XP_UNIX) || defined(XP_MAC) || defined(XP_OS2) || defined(XP_BEOS)
00167     // Some system calls require an EOF offset.
00168     PRInt64 offset;
00169     nsresult rv = Tell(&offset);
00170     if (NS_FAILED(rv)) return rv;
00171 #endif
00172 
00173 #if defined(XP_UNIX) || defined(XP_BEOS)
00174     if (ftruncate(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
00175         NS_ERROR("ftruncate failed");
00176         return NS_ERROR_FAILURE;
00177     }
00178 #elif defined(XP_MAC)
00179     if (::SetEOF(PR_FileDesc2NativeHandle(mFD), offset) != 0) {
00180         NS_ERROR("SetEOF failed");
00181         return NS_ERROR_FAILURE;
00182     }
00183 #elif defined(XP_WIN)
00184     if (!SetEndOfFile((HANDLE) PR_FileDesc2NativeHandle(mFD))) {
00185         NS_ERROR("SetEndOfFile failed");
00186         return NS_ERROR_FAILURE;
00187     }
00188 #elif defined(XP_OS2)
00189     if (DosSetFileSize((HFILE) PR_FileDesc2NativeHandle(mFD), offset) != NO_ERROR) {
00190         NS_ERROR("DosSetFileSize failed");
00191         return NS_ERROR_FAILURE;
00192     }
00193 #else
00194     // XXX not implemented
00195 #endif
00196 
00197     return NS_OK;
00198 }
00199 
00201 // nsFileInputStream
00202 
00203 NS_IMPL_ISUPPORTS_INHERITED3(nsFileInputStream, 
00204                              nsFileStream,
00205                              nsIInputStream,
00206                              nsIFileInputStream,
00207                              nsILineInputStream)
00208 
00209 NS_METHOD
00210 nsFileInputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
00211 {
00212     NS_ENSURE_NO_AGGREGATION(aOuter);
00213 
00214     nsFileInputStream* stream = new nsFileInputStream();
00215     if (stream == nsnull)
00216         return NS_ERROR_OUT_OF_MEMORY;
00217     NS_ADDREF(stream);
00218     nsresult rv = stream->QueryInterface(aIID, aResult);
00219     NS_RELEASE(stream);
00220     return rv;
00221 }
00222 
00223 nsresult
00224 nsFileInputStream::Open(nsIFile* aFile, PRInt32 aIOFlags, PRInt32 aPerm)
00225 {   
00226     nsresult rv = NS_OK;
00227 
00228     // If the previous file is open, close it
00229     if (mFD) {
00230         rv = Close();
00231         if (NS_FAILED(rv)) return rv;
00232     }
00233 
00234     // Open the file
00235     nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(aFile, &rv);
00236     if (NS_FAILED(rv)) return rv;
00237     if (aIOFlags == -1)
00238         aIOFlags = PR_RDONLY;
00239     if (aPerm == -1)
00240         aPerm = 0;
00241 
00242     PRFileDesc* fd;
00243     rv = localFile->OpenNSPRFileDesc(aIOFlags, aPerm, &fd);
00244     if (NS_FAILED(rv)) return rv;
00245 
00246     mFD = fd;
00247 
00248     if (mBehaviorFlags & DELETE_ON_CLOSE) {
00249         // POSIX compatible filesystems allow a file to be unlinked while a
00250         // file descriptor is still referencing the file.  since we've already
00251         // opened the file descriptor, we'll try to remove the file.  if that
00252         // fails, then we'll just remember the nsIFile and remove it after we
00253         // close the file descriptor.
00254         rv = aFile->Remove(PR_FALSE);
00255         if (NS_FAILED(rv) && !(mBehaviorFlags & REOPEN_ON_REWIND)) {
00256             // If REOPEN_ON_REWIND is not happenin', we haven't saved the file yet
00257             mFile = aFile;
00258         }
00259     }
00260 
00261     return NS_OK;
00262 }
00263 
00264 NS_IMETHODIMP
00265 nsFileInputStream::Init(nsIFile* aFile, PRInt32 aIOFlags, PRInt32 aPerm,
00266                         PRInt32 aBehaviorFlags)
00267 {
00268     NS_ENSURE_TRUE(!mFD, NS_ERROR_ALREADY_INITIALIZED);
00269     NS_ENSURE_TRUE(!mParent, NS_ERROR_ALREADY_INITIALIZED);
00270 
00271     mBehaviorFlags = aBehaviorFlags;
00272 
00273     // If the file will be reopened on rewind, save the info to open the file
00274     if (mBehaviorFlags & REOPEN_ON_REWIND) {
00275         mFile = aFile;
00276         mIOFlags = aIOFlags;
00277         mPerm = aPerm;
00278     }
00279 
00280     return Open(aFile, aIOFlags, aPerm);
00281 }
00282 
00283 NS_IMETHODIMP
00284 nsFileInputStream::Close()
00285 {
00286     // null out mLineBuffer in case Close() is called again after failing
00287     PR_FREEIF(mLineBuffer);
00288     nsresult rv = nsFileStream::Close();
00289     if (NS_FAILED(rv)) return rv;
00290     if (mFile && (mBehaviorFlags & DELETE_ON_CLOSE)) {
00291         rv = mFile->Remove(PR_FALSE);
00292         NS_ASSERTION(NS_SUCCEEDED(rv), "failed to delete file");
00293         // If we don't need to save the file for reopening, free it up
00294         if (!(mBehaviorFlags & REOPEN_ON_REWIND)) {
00295           mFile = nsnull;
00296         }
00297     }
00298     return rv;
00299 }
00300 
00301 NS_IMETHODIMP
00302 nsFileInputStream::Available(PRUint32* aResult)
00303 {
00304     if (!mFD) {
00305         return NS_BASE_STREAM_CLOSED;
00306     }
00307 
00308     PRInt32 avail = PR_Available(mFD);
00309     if (avail == -1) {
00310         return NS_ErrorAccordingToNSPR();
00311     }
00312     *aResult = avail;
00313     return NS_OK;
00314 }
00315 
00316 NS_IMETHODIMP
00317 nsFileInputStream::Read(char* aBuf, PRUint32 aCount, PRUint32* aResult)
00318 {
00319     if (!mFD) {
00320         return NS_BASE_STREAM_CLOSED;
00321     }
00322 
00323     PRInt32 bytesRead = PR_Read(mFD, aBuf, aCount);
00324     if (bytesRead == -1) {
00325         return NS_ErrorAccordingToNSPR();
00326     }
00327     // Check if we're at the end of file and need to close
00328     if (mBehaviorFlags & CLOSE_ON_EOF) {
00329         if (bytesRead == 0) {
00330             Close();
00331         }
00332     }
00333 
00334     *aResult = bytesRead;
00335     return NS_OK;
00336 }
00337 
00338 NS_IMETHODIMP
00339 nsFileInputStream::ReadLine(nsACString& aLine, PRBool* aResult)
00340 {
00341     if (!mLineBuffer) {
00342         nsresult rv = NS_InitLineBuffer(&mLineBuffer);
00343         if (NS_FAILED(rv)) return rv;
00344     }
00345     return NS_ReadLine(this, mLineBuffer, aLine, aResult);
00346 }
00347 
00348 NS_IMETHODIMP
00349 nsFileInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
00350                                 PRUint32 aCount, PRUint32* aResult)
00351 {
00352     // ReadSegments is not implemented because it would be inefficient when
00353     // the writer does not consume all data.  If you want to call ReadSegments,
00354     // wrap a BufferedInputStream around the file stream.  That will call
00355     // Read().
00356     return NS_ERROR_NOT_IMPLEMENTED;
00357 }
00358 
00359 NS_IMETHODIMP
00360 nsFileInputStream::IsNonBlocking(PRBool *aNonBlocking)
00361 {
00362     *aNonBlocking = PR_FALSE;
00363     return NS_OK;
00364 }
00365 
00366 NS_IMETHODIMP
00367 nsFileInputStream::Seek(PRInt32 aWhence, PRInt64 aOffset)
00368 {
00369     PR_FREEIF(mLineBuffer); // this invalidates the line buffer
00370     if (!mFD) {
00371         if (mBehaviorFlags & REOPEN_ON_REWIND) {
00372             nsresult rv = Reopen();
00373             if (NS_FAILED(rv)) {
00374                 return rv;
00375             }
00376         } else {
00377             return NS_BASE_STREAM_CLOSED;
00378         }
00379     }
00380 
00381     return nsFileStream::Seek(aWhence, aOffset);
00382 }
00383 
00385 // nsFileOutputStream
00386 
00387 NS_IMPL_ISUPPORTS_INHERITED2(nsFileOutputStream, 
00388                              nsFileStream,
00389                              nsIOutputStream,
00390                              nsIFileOutputStream)
00391  
00392 NS_METHOD
00393 nsFileOutputStream::Create(nsISupports *aOuter, REFNSIID aIID, void **aResult)
00394 {
00395     NS_ENSURE_NO_AGGREGATION(aOuter);
00396 
00397     nsFileOutputStream* stream = new nsFileOutputStream();
00398     if (stream == nsnull)
00399         return NS_ERROR_OUT_OF_MEMORY;
00400     NS_ADDREF(stream);
00401     nsresult rv = stream->QueryInterface(aIID, aResult);
00402     NS_RELEASE(stream);
00403     return rv;
00404 }
00405 
00406 NS_IMETHODIMP
00407 nsFileOutputStream::Init(nsIFile* file, PRInt32 ioFlags, PRInt32 perm,
00408                          PRInt32 behaviorFlags)
00409 {
00410     NS_ENSURE_TRUE(mFD == nsnull, NS_ERROR_ALREADY_INITIALIZED);
00411 
00412     nsresult rv;
00413     nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(file, &rv);
00414     if (NS_FAILED(rv)) return rv;
00415     if (ioFlags == -1)
00416         ioFlags = PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE;
00417     if (perm <= 0)
00418         perm = 0664;
00419 
00420     PRFileDesc* fd;
00421     rv = localFile->OpenNSPRFileDesc(ioFlags, perm, &fd);
00422     if (NS_FAILED(rv)) return rv;
00423 
00424     mFD = fd;
00425     return NS_OK;
00426 }
00427 
00428 NS_IMETHODIMP
00429 nsFileOutputStream::Close()
00430 {
00431     return nsFileStream::Close();
00432 }
00433 
00434 NS_IMETHODIMP
00435 nsFileOutputStream::Write(const char *buf, PRUint32 count, PRUint32 *result)
00436 {
00437     if (mFD == nsnull)
00438         return NS_BASE_STREAM_CLOSED;
00439 
00440     PRInt32 cnt = PR_Write(mFD, buf, count);
00441     if (cnt == -1) {
00442         return NS_ErrorAccordingToNSPR();
00443     }
00444     *result = cnt;
00445     return NS_OK;
00446 }
00447 
00448 NS_IMETHODIMP
00449 nsFileOutputStream::Flush(void)
00450 {
00451     if (mFD == nsnull)
00452         return NS_BASE_STREAM_CLOSED;
00453 
00454     PRInt32 cnt = PR_Sync(mFD);
00455     if (cnt == -1) {
00456         return NS_ErrorAccordingToNSPR();
00457     }
00458     return NS_OK;
00459 }
00460     
00461 NS_IMETHODIMP
00462 nsFileOutputStream::WriteFrom(nsIInputStream *inStr, PRUint32 count, PRUint32 *_retval)
00463 {
00464     NS_NOTREACHED("WriteFrom (see source comment)");
00465     return NS_ERROR_NOT_IMPLEMENTED;
00466     // File streams intentionally do not support this method.
00467     // If you need something like this, then you should wrap
00468     // the file stream using nsIBufferedOutputStream
00469 }
00470 
00471 NS_IMETHODIMP
00472 nsFileOutputStream::WriteSegments(nsReadSegmentFun reader, void * closure, PRUint32 count, PRUint32 *_retval)
00473 {
00474     NS_NOTREACHED("WriteSegments (see source comment)");
00475     return NS_ERROR_NOT_IMPLEMENTED;
00476     // File streams intentionally do not support this method.
00477     // If you need something like this, then you should wrap
00478     // the file stream using nsIBufferedOutputStream
00479 }
00480 
00481 NS_IMETHODIMP
00482 nsFileOutputStream::IsNonBlocking(PRBool *aNonBlocking)
00483 {
00484     *aNonBlocking = PR_FALSE;
00485     return NS_OK;
00486 }
00487 
00489 // nsSafeFileOutputStream
00490 
00491 NS_IMPL_ISUPPORTS_INHERITED3(nsSafeFileOutputStream, 
00492                              nsFileOutputStream,
00493                              nsISafeOutputStream,
00494                              nsIOutputStream,
00495                              nsIFileOutputStream)
00496 
00497 NS_IMETHODIMP
00498 nsSafeFileOutputStream::Init(nsIFile* file, PRInt32 ioFlags, PRInt32 perm,
00499                              PRInt32 behaviorFlags)
00500 {
00501     NS_ENSURE_ARG(file);
00502 
00503     nsresult rv = file->Exists(&mTargetFileExists);
00504     if (NS_FAILED(rv)) {
00505         NS_ERROR("Can't tell if target file exists");
00506         mTargetFileExists = PR_TRUE; // Safer to assume it exists - we just do more work.
00507     }
00508 
00509     // follow symlinks, for two reasons:
00510     // 1) if a user has deliberately set up a profile file as a symlink, we honor it
00511     // 2) to make the MoveToNative() in Finish() an atomic operation (which may not
00512     //    be the case if moving across directories on different filesystems).
00513     nsCOMPtr<nsIFile> tempResult;
00514     rv = file->Clone(getter_AddRefs(tempResult));
00515     if (NS_SUCCEEDED(rv)) {
00516         nsCOMPtr<nsILocalFile> tempLocal = do_QueryInterface(tempResult);
00517         if (tempLocal)
00518             tempLocal->SetFollowLinks(PR_TRUE);
00519 
00520         // XP_UNIX ignores SetFollowLinks(), so we have to normalize.
00521         tempResult->Normalize();
00522     }
00523 
00524     if (NS_SUCCEEDED(rv) && mTargetFileExists) {
00525         PRUint32 origPerm;
00526         if (NS_FAILED(file->GetPermissions(&origPerm))) {
00527             NS_ERROR("Can't get permissions of target file");
00528             origPerm = perm;
00529         }
00530         // XXX What if |perm| is more restrictive then |origPerm|?
00531         // This leaves the user supplied permissions as they were.
00532         rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm);
00533     }
00534     if (NS_SUCCEEDED(rv)) {
00535         mTempFile = tempResult;
00536         mTargetFile = file;
00537         rv = nsFileOutputStream::Init(mTempFile, ioFlags, perm, behaviorFlags);
00538     }
00539     return rv;
00540 }
00541 
00542 NS_IMETHODIMP
00543 nsSafeFileOutputStream::Close()
00544 {
00545     nsresult rv = nsFileOutputStream::Close();
00546 
00547     // the consumer doesn't want the original file overwritten -
00548     // so clean up by removing the temp file.
00549     if (mTempFile) {
00550         mTempFile->Remove(PR_FALSE);
00551         mTempFile = nsnull;
00552     }
00553 
00554     return rv;
00555 }
00556 
00557 NS_IMETHODIMP
00558 nsSafeFileOutputStream::Finish()
00559 {
00560     nsresult rv = nsFileOutputStream::Close();
00561 
00562     // if there is no temp file, don't try to move it over the original target.
00563     // It would destroy the targetfile if close() is called twice.
00564     if (!mTempFile)
00565         return rv;
00566 
00567     // Only overwrite if everything was ok, and the temp file could be closed.
00568     if (NS_SUCCEEDED(mWriteResult) && NS_SUCCEEDED(rv)) {
00569         NS_ENSURE_STATE(mTargetFile);
00570 
00571         if (!mTargetFileExists) {
00572             // If the target file did not exist when we were initialized, then the
00573             // temp file we gave out was actually a reference to the target file.
00574             // since we succeeded in writing to the temp file (and hence succeeded
00575             // in writing to the target file), there is nothing more to do.
00576 #ifdef DEBUG      
00577             PRBool equal;
00578             if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal)
00579                 NS_ERROR("mTempFile not equal to mTargetFile");
00580 #endif
00581         }
00582         else {
00583             nsCAutoString targetFilename;
00584             rv = mTargetFile->GetNativeLeafName(targetFilename);
00585             if (NS_SUCCEEDED(rv)) {
00586                 // This will replace target.
00587                 rv = mTempFile->MoveToNative(nsnull, targetFilename);
00588                 if (NS_FAILED(rv))
00589                     mTempFile->Remove(PR_FALSE);
00590             }
00591         }
00592     }
00593     else {
00594         mTempFile->Remove(PR_FALSE);
00595 
00596         // if writing failed, propagate the failure code to the caller.
00597         if (NS_FAILED(mWriteResult))
00598             rv = mWriteResult;
00599     }
00600     mTempFile = nsnull;
00601     return rv;
00602 }
00603 
00604 NS_IMETHODIMP
00605 nsSafeFileOutputStream::Write(const char *buf, PRUint32 count, PRUint32 *result)
00606 {
00607     nsresult rv = nsFileOutputStream::Write(buf, count, result);
00608     if (NS_SUCCEEDED(mWriteResult)) {
00609         if (NS_FAILED(rv))
00610             mWriteResult = rv;
00611         else if (count != *result)
00612             mWriteResult = NS_ERROR_LOSS_OF_SIGNIFICANT_DATA;
00613 
00614         if (NS_FAILED(mWriteResult) && count > 0)
00615             NS_WARNING("writing to output stream failed! data may be lost");
00616     } 
00617     return rv;
00618 }
00619