1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 #if defined(XP_UNIX) || defined(XP_BEOS)
43 #define INCL_DOSERRORS
46 // XXX add necessary include file for ftruncate (or equivalent)
49 #include "private/pprio.h"
51 #include "nsFileStreams.h"
52 #include "nsILocalFile.h"
53 #include "nsXPIDLString.h"
58 #include "nsDirectoryIndexStream.h"
59 #include "nsMimeTypes.h"
60 #include "nsReadLine.h"
61 #include "nsNetUtil.h"
62 //#include "nsFileTransportService.h"
64 #define NS_NO_INPUT_BUFFERING 1 // see http://bugzilla.mozilla.org/show_bug.cgi?id=41067
66 ////////////////////////////////////////////////////////////////////////////////
69 nsFileStream::nsFileStream()
75 nsFileStream::~nsFileStream()
81 NS_IMPL_THREADSAFE_ISUPPORTS1(nsFileStream
, nsISeekableStream
)
84 nsFileStream::InitWithFileDescriptor(PRFileDesc
* fd
, nsISupports
* parent
)
86 NS_ENSURE_TRUE(mFD
== nsnull
, NS_ERROR_ALREADY_INITIALIZED
);
88 // this file stream is dependent on its parent to keep the
89 // file descriptor valid. an owning reference to the parent
90 // prevents the file descriptor from going away prematurely.
104 if (PR_Close(mFD
) == PR_FAILURE
)
105 rv
= NS_BASE_STREAM_OSERROR
;
112 nsFileStream::Seek(PRInt32 whence
, PRInt64 offset
)
115 return NS_BASE_STREAM_CLOSED
;
117 nsInt64 cnt
= PR_Seek64(mFD
, offset
, (PRSeekWhence
)whence
);
118 if (cnt
== nsInt64(-1)) {
119 return NS_ErrorAccordingToNSPR();
125 nsFileStream::Tell(PRInt64
*result
)
128 return NS_BASE_STREAM_CLOSED
;
130 nsInt64 cnt
= PR_Seek64(mFD
, 0, PR_SEEK_CUR
);
131 if (cnt
== nsInt64(-1)) {
132 return NS_ErrorAccordingToNSPR();
139 nsFileStream::SetEOF()
142 return NS_BASE_STREAM_CLOSED
;
144 #if defined(XP_UNIX) || defined(XP_OS2) || defined(XP_BEOS)
145 // Some system calls require an EOF offset.
147 nsresult rv
= Tell(&offset
);
148 if (NS_FAILED(rv
)) return rv
;
151 #if defined(XP_UNIX) || defined(XP_BEOS)
152 if (ftruncate(PR_FileDesc2NativeHandle(mFD
), offset
) != 0) {
153 NS_ERROR("ftruncate failed");
154 return NS_ERROR_FAILURE
;
156 #elif defined(XP_WIN)
157 if (!SetEndOfFile((HANDLE
) PR_FileDesc2NativeHandle(mFD
))) {
158 NS_ERROR("SetEndOfFile failed");
159 return NS_ERROR_FAILURE
;
161 #elif defined(XP_OS2)
162 if (DosSetFileSize((HFILE
) PR_FileDesc2NativeHandle(mFD
), offset
) != NO_ERROR
) {
163 NS_ERROR("DosSetFileSize failed");
164 return NS_ERROR_FAILURE
;
167 // XXX not implemented
173 ////////////////////////////////////////////////////////////////////////////////
176 NS_IMPL_ISUPPORTS_INHERITED3(nsFileInputStream
,
183 nsFileInputStream::Create(nsISupports
*aOuter
, REFNSIID aIID
, void **aResult
)
185 NS_ENSURE_NO_AGGREGATION(aOuter
);
187 nsFileInputStream
* stream
= new nsFileInputStream();
188 if (stream
== nsnull
)
189 return NS_ERROR_OUT_OF_MEMORY
;
191 nsresult rv
= stream
->QueryInterface(aIID
, aResult
);
197 nsFileInputStream::Open(nsIFile
* aFile
, PRInt32 aIOFlags
, PRInt32 aPerm
)
201 // If the previous file is open, close it
204 if (NS_FAILED(rv
)) return rv
;
208 nsCOMPtr
<nsILocalFile
> localFile
= do_QueryInterface(aFile
, &rv
);
209 if (NS_FAILED(rv
)) return rv
;
211 aIOFlags
= PR_RDONLY
;
216 rv
= localFile
->OpenNSPRFileDesc(aIOFlags
, aPerm
, &fd
);
217 if (NS_FAILED(rv
)) return rv
;
221 if (mBehaviorFlags
& DELETE_ON_CLOSE
) {
222 // POSIX compatible filesystems allow a file to be unlinked while a
223 // file descriptor is still referencing the file. since we've already
224 // opened the file descriptor, we'll try to remove the file. if that
225 // fails, then we'll just remember the nsIFile and remove it after we
226 // close the file descriptor.
227 rv
= aFile
->Remove(PR_FALSE
);
228 if (NS_FAILED(rv
) && !(mBehaviorFlags
& REOPEN_ON_REWIND
)) {
229 // If REOPEN_ON_REWIND is not happenin', we haven't saved the file yet
238 nsFileInputStream::Init(nsIFile
* aFile
, PRInt32 aIOFlags
, PRInt32 aPerm
,
239 PRInt32 aBehaviorFlags
)
241 NS_ENSURE_TRUE(!mFD
, NS_ERROR_ALREADY_INITIALIZED
);
242 NS_ENSURE_TRUE(!mParent
, NS_ERROR_ALREADY_INITIALIZED
);
244 mBehaviorFlags
= aBehaviorFlags
;
246 // If the file will be reopened on rewind, save the info to open the file
247 if (mBehaviorFlags
& REOPEN_ON_REWIND
) {
253 return Open(aFile
, aIOFlags
, aPerm
);
257 nsFileInputStream::Close()
259 // null out mLineBuffer in case Close() is called again after failing
260 PR_FREEIF(mLineBuffer
);
261 nsresult rv
= nsFileStream::Close();
262 if (NS_FAILED(rv
)) return rv
;
263 if (mFile
&& (mBehaviorFlags
& DELETE_ON_CLOSE
)) {
264 rv
= mFile
->Remove(PR_FALSE
);
265 NS_ASSERTION(NS_SUCCEEDED(rv
), "failed to delete file");
266 // If we don't need to save the file for reopening, free it up
267 if (!(mBehaviorFlags
& REOPEN_ON_REWIND
)) {
275 nsFileInputStream::Available(PRUint32
* aResult
)
278 return NS_BASE_STREAM_CLOSED
;
281 PRInt32 avail
= PR_Available(mFD
);
283 return NS_ErrorAccordingToNSPR();
290 nsFileInputStream::Read(char* aBuf
, PRUint32 aCount
, PRUint32
* aResult
)
297 PRInt32 bytesRead
= PR_Read(mFD
, aBuf
, aCount
);
298 if (bytesRead
== -1) {
299 return NS_ErrorAccordingToNSPR();
301 // Check if we're at the end of file and need to close
302 if (mBehaviorFlags
& CLOSE_ON_EOF
) {
303 if (bytesRead
== 0) {
308 *aResult
= bytesRead
;
313 nsFileInputStream::ReadLine(nsACString
& aLine
, PRBool
* aResult
)
316 nsresult rv
= NS_InitLineBuffer(&mLineBuffer
);
317 if (NS_FAILED(rv
)) return rv
;
319 return NS_ReadLine(this, mLineBuffer
, aLine
, aResult
);
323 nsFileInputStream::ReadSegments(nsWriteSegmentFun aWriter
, void* aClosure
,
324 PRUint32 aCount
, PRUint32
* aResult
)
326 // ReadSegments is not implemented because it would be inefficient when
327 // the writer does not consume all data. If you want to call ReadSegments,
328 // wrap a BufferedInputStream around the file stream. That will call
330 return NS_ERROR_NOT_IMPLEMENTED
;
334 nsFileInputStream::IsNonBlocking(PRBool
*aNonBlocking
)
336 *aNonBlocking
= PR_FALSE
;
341 nsFileInputStream::Seek(PRInt32 aWhence
, PRInt64 aOffset
)
343 PR_FREEIF(mLineBuffer
); // this invalidates the line buffer
345 if (mBehaviorFlags
& REOPEN_ON_REWIND
) {
346 nsresult rv
= Reopen();
351 return NS_BASE_STREAM_CLOSED
;
355 return nsFileStream::Seek(aWhence
, aOffset
);
358 ////////////////////////////////////////////////////////////////////////////////
359 // nsFileOutputStream
361 NS_IMPL_ISUPPORTS_INHERITED2(nsFileOutputStream
,
367 nsFileOutputStream::Create(nsISupports
*aOuter
, REFNSIID aIID
, void **aResult
)
369 NS_ENSURE_NO_AGGREGATION(aOuter
);
371 nsFileOutputStream
* stream
= new nsFileOutputStream();
372 if (stream
== nsnull
)
373 return NS_ERROR_OUT_OF_MEMORY
;
375 nsresult rv
= stream
->QueryInterface(aIID
, aResult
);
381 nsFileOutputStream::Init(nsIFile
* file
, PRInt32 ioFlags
, PRInt32 perm
,
382 PRInt32 behaviorFlags
)
384 NS_ENSURE_TRUE(mFD
== nsnull
, NS_ERROR_ALREADY_INITIALIZED
);
387 nsCOMPtr
<nsILocalFile
> localFile
= do_QueryInterface(file
, &rv
);
388 if (NS_FAILED(rv
)) return rv
;
390 ioFlags
= PR_WRONLY
| PR_CREATE_FILE
| PR_TRUNCATE
;
395 rv
= localFile
->OpenNSPRFileDesc(ioFlags
, perm
, &fd
);
396 if (NS_FAILED(rv
)) return rv
;
403 nsFileOutputStream::Close()
405 return nsFileStream::Close();
409 nsFileOutputStream::Write(const char *buf
, PRUint32 count
, PRUint32
*result
)
412 return NS_BASE_STREAM_CLOSED
;
414 PRInt32 cnt
= PR_Write(mFD
, buf
, count
);
416 return NS_ErrorAccordingToNSPR();
423 nsFileOutputStream::Flush(void)
426 return NS_BASE_STREAM_CLOSED
;
428 PRInt32 cnt
= PR_Sync(mFD
);
430 return NS_ErrorAccordingToNSPR();
436 nsFileOutputStream::WriteFrom(nsIInputStream
*inStr
, PRUint32 count
, PRUint32
*_retval
)
438 NS_NOTREACHED("WriteFrom (see source comment)");
439 return NS_ERROR_NOT_IMPLEMENTED
;
440 // File streams intentionally do not support this method.
441 // If you need something like this, then you should wrap
442 // the file stream using nsIBufferedOutputStream
446 nsFileOutputStream::WriteSegments(nsReadSegmentFun reader
, void * closure
, PRUint32 count
, PRUint32
*_retval
)
448 NS_NOTREACHED("WriteSegments (see source comment)");
449 return NS_ERROR_NOT_IMPLEMENTED
;
450 // File streams intentionally do not support this method.
451 // If you need something like this, then you should wrap
452 // the file stream using nsIBufferedOutputStream
456 nsFileOutputStream::IsNonBlocking(PRBool
*aNonBlocking
)
458 *aNonBlocking
= PR_FALSE
;
462 ////////////////////////////////////////////////////////////////////////////////
463 // nsSafeFileOutputStream
465 NS_IMPL_ISUPPORTS_INHERITED3(nsSafeFileOutputStream
,
472 nsSafeFileOutputStream::Init(nsIFile
* file
, PRInt32 ioFlags
, PRInt32 perm
,
473 PRInt32 behaviorFlags
)
477 nsresult rv
= file
->Exists(&mTargetFileExists
);
479 NS_ERROR("Can't tell if target file exists");
480 mTargetFileExists
= PR_TRUE
; // Safer to assume it exists - we just do more work.
483 // follow symlinks, for two reasons:
484 // 1) if a user has deliberately set up a profile file as a symlink, we honor it
485 // 2) to make the MoveToNative() in Finish() an atomic operation (which may not
486 // be the case if moving across directories on different filesystems).
487 nsCOMPtr
<nsIFile
> tempResult
;
488 rv
= file
->Clone(getter_AddRefs(tempResult
));
489 if (NS_SUCCEEDED(rv
)) {
490 nsCOMPtr
<nsILocalFile
> tempLocal
= do_QueryInterface(tempResult
);
492 tempLocal
->SetFollowLinks(PR_TRUE
);
494 // XP_UNIX ignores SetFollowLinks(), so we have to normalize.
495 tempResult
->Normalize();
498 if (NS_SUCCEEDED(rv
) && mTargetFileExists
) {
500 if (NS_FAILED(file
->GetPermissions(&origPerm
))) {
501 NS_ERROR("Can't get permissions of target file");
504 // XXX What if |perm| is more restrictive then |origPerm|?
505 // This leaves the user supplied permissions as they were.
506 rv
= tempResult
->CreateUnique(nsIFile::NORMAL_FILE_TYPE
, origPerm
);
508 if (NS_SUCCEEDED(rv
)) {
509 mTempFile
= tempResult
;
511 rv
= nsFileOutputStream::Init(mTempFile
, ioFlags
, perm
, behaviorFlags
);
517 nsSafeFileOutputStream::Close()
519 nsresult rv
= nsFileOutputStream::Close();
521 // the consumer doesn't want the original file overwritten -
522 // so clean up by removing the temp file.
524 mTempFile
->Remove(PR_FALSE
);
532 nsSafeFileOutputStream::Finish()
534 nsresult rv
= nsFileOutputStream::Close();
536 // if there is no temp file, don't try to move it over the original target.
537 // It would destroy the targetfile if close() is called twice.
541 // Only overwrite if everything was ok, and the temp file could be closed.
542 if (NS_SUCCEEDED(mWriteResult
) && NS_SUCCEEDED(rv
)) {
543 NS_ENSURE_STATE(mTargetFile
);
545 if (!mTargetFileExists
) {
546 // If the target file did not exist when we were initialized, then the
547 // temp file we gave out was actually a reference to the target file.
548 // since we succeeded in writing to the temp file (and hence succeeded
549 // in writing to the target file), there is nothing more to do.
552 if (NS_FAILED(mTargetFile
->Equals(mTempFile
, &equal
)) || !equal
)
553 NS_ERROR("mTempFile not equal to mTargetFile");
557 nsCAutoString targetFilename
;
558 rv
= mTargetFile
->GetNativeLeafName(targetFilename
);
559 if (NS_SUCCEEDED(rv
)) {
560 // This will replace target.
561 rv
= mTempFile
->MoveToNative(nsnull
, targetFilename
);
563 mTempFile
->Remove(PR_FALSE
);
568 mTempFile
->Remove(PR_FALSE
);
570 // if writing failed, propagate the failure code to the caller.
571 if (NS_FAILED(mWriteResult
))
579 nsSafeFileOutputStream::Write(const char *buf
, PRUint32 count
, PRUint32
*result
)
581 nsresult rv
= nsFileOutputStream::Write(buf
, count
, result
);
582 if (NS_SUCCEEDED(mWriteResult
)) {
585 else if (count
!= *result
)
586 mWriteResult
= NS_ERROR_LOSS_OF_SIGNIFICANT_DATA
;
588 if (NS_FAILED(mWriteResult
) && count
> 0)
589 NS_WARNING("writing to output stream failed! data may be lost");
594 ////////////////////////////////////////////////////////////////////////////////