From 5229c8ccbbf00d9906a3e14b05e908951349a27c Mon Sep 17 00:00:00 2001 From: "mvl%exedo.nl" Date: Tue, 6 Jul 2004 20:35:40 +0000 Subject: [PATCH] create a non-overwriting file output stream bug 246675, r=biesi, sr=darin --- netwerk/base/public/nsIFileStreams.idl | 33 ++++++++ netwerk/base/public/nsNetUtil.h | 21 +++++ netwerk/base/src/nsFileStreams.cpp | 102 +++++++++++++++++++++++++ netwerk/base/src/nsFileStreams.h | 28 +++++++ netwerk/build/nsNetCID.h | 12 +++ netwerk/build/nsNetModule.cpp | 7 ++ netwerk/cookie/src/nsCookieService.cpp | 8 +- 7 files changed, 210 insertions(+), 1 deletion(-) diff --git a/netwerk/base/public/nsIFileStreams.idl b/netwerk/base/public/nsIFileStreams.idl index 11f5aa0d4fe8..747c08172166 100644 --- a/netwerk/base/public/nsIFileStreams.idl +++ b/netwerk/base/public/nsIFileStreams.idl @@ -96,3 +96,36 @@ interface nsIFileOutputStream : nsIOutputStream void init(in nsIFile file, in long ioFlags, in long perm, in long behaviorFlags); }; + +/** + * This interface provides a mechanism to control a file output stream + * that takes care not to overwrite an existing file until it is known + * that all writes to the file succeeded. + * + * An object that supports this interface is intended to also support + * nsIFileOutputStream. + * + * A file output stream that supports this interface writes to a + * temporary file, and moves it over the original file when the stream + * is closed, and all writes succeeded. If the stream is closed, and + * something went wrong during writing, it will delete the temporary + * file and not touch the original. + */ + +[scriptable, uuid(5f914307-5c34-4e1f-8e32-ec749d25b27a)] +interface nsISafeFileOutputStream : nsISupports +{ + /** + * Set this attribute to true to cause the original file to be + * overwritten. Note: if any call to |write| failed to write out + * all of the data given to it, then setting this attribute to + * true will be ignored. The file will only be saved if all calls + * to |write| succeeded and if this attribute is set to true. + * If this attribute isn't set before |close| is called, + * or the stream goes away, the original file will not be + * overwritten, and the temporary file will be deleted. + * + * The default value for this attribute is false. + */ + attribute boolean writeSucceeded; +}; diff --git a/netwerk/base/public/nsNetUtil.h b/netwerk/base/public/nsNetUtil.h index c88b73dd0ce5..dcddd3950202 100644 --- a/netwerk/base/public/nsNetUtil.h +++ b/netwerk/base/public/nsNetUtil.h @@ -730,6 +730,27 @@ NS_NewLocalFileOutputStream(nsIOutputStream **aResult, return rv; } +// Returns a file output stream. The object can be QI-ed to +// nsISafeFileOuputStream. +inline nsresult +NS_NewSafeLocalFileOutputStream(nsIOutputStream **aResult, + nsIFile *aFile, + PRInt32 aIOFlags = -1, + PRInt32 aPerm = -1, + PRInt32 aBehaviorFlags = 0) +{ + nsresult rv; + static NS_DEFINE_CID(kSafeLocalFileOutputStreamCID, NS_SAFELOCALFILEOUTPUTSTREAM_CID); + nsCOMPtr out = + do_CreateInstance(kSafeLocalFileOutputStreamCID, &rv); + if (NS_SUCCEEDED(rv)) { + rv = out->Init(aFile, aIOFlags, aPerm, aBehaviorFlags); + if (NS_SUCCEEDED(rv)) + NS_ADDREF(*aResult = out); + } + return rv; +} + // returns the input end of a pipe. the output end of the pipe // is attached to the original stream. data from the original // stream is read into the pipe on a background thread. diff --git a/netwerk/base/src/nsFileStreams.cpp b/netwerk/base/src/nsFileStreams.cpp index e60acb591991..12d1323451e9 100644 --- a/netwerk/base/src/nsFileStreams.cpp +++ b/netwerk/base/src/nsFileStreams.cpp @@ -487,3 +487,105 @@ nsFileOutputStream::IsNonBlocking(PRBool *aNonBlocking) } //////////////////////////////////////////////////////////////////////////////// +// nsFileOutputStream + +NS_IMPL_ISUPPORTS_INHERITED3(nsSafeFileOutputStream, + nsFileOutputStream, + nsISafeFileOutputStream, + nsIOutputStream, + nsIFileOutputStream) + +NS_IMETHODIMP +nsSafeFileOutputStream::Init(nsIFile* file, PRInt32 ioFlags, PRInt32 perm, + PRInt32 behaviorFlags) +{ + nsresult rv = file->Exists(&mTargetFileExists); + if (NS_FAILED(rv)) { + NS_ERROR("Can't tell if target file exists"); + mTargetFileExists = PR_TRUE; // Safer to assume it exists - we just do more work. + } + + nsCOMPtr tempResult; + rv = file->Clone(getter_AddRefs(tempResult)); + if (NS_SUCCEEDED(rv) && mTargetFileExists) { + PRUint32 origPerm; + if (NS_FAILED(file->GetPermissions(&origPerm))) { + NS_ERROR("Can't get permissions of target file"); + origPerm = perm; + } + // XXX What if |perm| is more restrictive then |origPerm|? + // This leaves the user supplied permssions as they were. + rv = tempResult->CreateUnique(nsIFile::NORMAL_FILE_TYPE, origPerm); + } + if (NS_SUCCEEDED(rv)) { + mTempFile = tempResult; + mTargetFile = file; + rv = nsFileOutputStream::Init(mTempFile, ioFlags, perm, behaviorFlags); + } + return rv; +} + +NS_IMETHODIMP +nsSafeFileOutputStream::SetWriteSucceeded(PRBool aWriteSucceeded) +{ + mWriteSucceeded = aWriteSucceeded; + return NS_OK; +} + +NS_IMETHODIMP +nsSafeFileOutputStream::GetWriteSucceeded(PRBool *aWriteSucceeded) +{ + *aWriteSucceeded = mWriteSucceeded && mInternalWriteSucceeded; + return NS_OK; +} + +NS_IMETHODIMP +nsSafeFileOutputStream::Close() +{ + nsresult rv = nsFileOutputStream::Close(); + + // if there is no temp file, don't try to move it over the original target. + // It would destroy the targetfile if close() is called twice. + if (!mTempFile) + return NS_OK; + + // Only overwrite if everything was ok, and the temp file could be closed. + if (mWriteSucceeded && mInternalWriteSucceeded && NS_SUCCEEDED(rv)) { + NS_ENSURE_STATE(mTargetFile); + + if (!mTargetFileExists) { + // If the target file did not exist when we were initialized, then the + // temp file we gave out was actually a reference to the target file. + // since we succeeded in writing to the temp file (and hence succeeded + // in writing to the target file), there is nothing more to do. +#ifdef DEBUG + PRBool equal; + if (NS_FAILED(mTargetFile->Equals(mTempFile, &equal)) || !equal) + NS_ERROR("mTempFile not equal to mTargetFile"); +#endif + return NS_OK; + } + + nsCAutoString targetFilename; + rv = mTargetFile->GetNativeLeafName(targetFilename); + + if (NS_SUCCEEDED(rv)) + rv = mTempFile->MoveToNative(nsnull, targetFilename); // This will replace target + } + else { + rv = mTempFile->Remove(PR_FALSE); + } + mTempFile = nsnull; + return rv; +} + +NS_IMETHODIMP +nsSafeFileOutputStream::Write(const char *buf, PRUint32 count, PRUint32 *result) +{ + nsresult rv = nsFileOutputStream::Write(buf, count, result); + if (NS_FAILED(rv) || count != *result) + mInternalWriteSucceeded = PR_FALSE; + return rv; +} + +//////////////////////////////////////////////////////////////////////////////// diff --git a/netwerk/base/src/nsFileStreams.h b/netwerk/base/src/nsFileStreams.h index 9ce110aa55d7..96413860684e 100644 --- a/netwerk/base/src/nsFileStreams.h +++ b/netwerk/base/src/nsFileStreams.h @@ -153,4 +153,32 @@ public: //////////////////////////////////////////////////////////////////////////////// +class nsSafeFileOutputStream : public nsFileOutputStream, + public nsISafeFileOutputStream +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISAFEFILEOUTPUTSTREAM + + nsSafeFileOutputStream() : + mWriteSucceeded(PR_FALSE), + mInternalWriteSucceeded(PR_TRUE) {} + + virtual ~nsSafeFileOutputStream() { nsSafeFileOutputStream::Close(); } + + NS_IMETHODIMP Close(); + NS_IMETHODIMP Write(const char *buf, PRUint32 count, PRUint32 *result); + NS_IMETHODIMP Init(nsIFile* file, PRInt32 ioFlags, PRInt32 perm, PRInt32 behaviorFlags); + +protected: + nsCOMPtr mTargetFile; + nsCOMPtr mTempFile; + + PRBool mTargetFileExists; + PRBool mWriteSucceeded; // Consumer reported + PRBool mInternalWriteSucceeded; // Internally detected in Write() +}; + +//////////////////////////////////////////////////////////////////////////////// + #endif // nsFileStreams_h__ diff --git a/netwerk/build/nsNetCID.h b/netwerk/build/nsNetCID.h index 1caf2eb8e56a..6a726a694f2b 100644 --- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -435,6 +435,18 @@ {0x8c, 0xda, 0x00, 0x60, 0xb0, 0xfc, 0x14, 0xa3} \ } +// component implementing nsISafeFileOutputStream +#define NS_SAFELOCALFILEOUTPUTSTREAM_CLASSNAME \ + "nsSafeFileOutputStream" +#define NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID \ + "@mozilla.org/network/safe-file-output-stream;1" +#define NS_SAFELOCALFILEOUTPUTSTREAM_CID \ +{ /* a181af0d-68b8-4308-94db-d4f859058215 */ \ + 0xa181af0d, \ + 0x68b8, \ + 0x4308, \ + {0x94, 0xdb, 0xd4, 0xf8, 0x59, 0x05, 0x82, 0x15} \ +} /****************************************************************************** * netwerk/cache/ classes diff --git a/netwerk/build/nsNetModule.cpp b/netwerk/build/nsNetModule.cpp index e4312ebd6a2e..cb05fb2b2efd 100644 --- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -105,6 +105,8 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(nsDownloader) #include "nsSyncStreamListener.h" NS_GENERIC_FACTORY_CONSTRUCTOR_INIT(nsSyncStreamListener, Init) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsSafeFileOutputStream) + /////////////////////////////////////////////////////////////////////////////// #include "nsStreamConverterService.h" @@ -687,6 +689,11 @@ static const nsModuleComponentInfo gNetModuleInfo[] = { NS_LOCALFILEOUTPUTSTREAM_CID, NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, nsFileOutputStream::Create }, + { NS_SAFELOCALFILEOUTPUTSTREAM_CLASSNAME, + NS_SAFELOCALFILEOUTPUTSTREAM_CID, + NS_SAFELOCALFILEOUTPUTSTREAM_CONTRACTID, + nsSafeFileOutputStreamConstructor + }, { "URIChecker", NS_URICHECKER_CID, diff --git a/netwerk/cookie/src/nsCookieService.cpp b/netwerk/cookie/src/nsCookieService.cpp index f748203077db..9131e5cda033 100644 --- a/netwerk/cookie/src/nsCookieService.cpp +++ b/netwerk/cookie/src/nsCookieService.cpp @@ -1039,7 +1039,7 @@ nsCookieService::Write() nsresult rv; nsCOMPtr fileOutputStream; - rv = NS_NewLocalFileOutputStream(getter_AddRefs(fileOutputStream), mCookieFile); + rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(fileOutputStream), mCookieFile); if (NS_FAILED(rv)) { NS_ERROR("failed to open cookies.txt for writing"); return rv; @@ -1115,6 +1115,12 @@ nsCookieService::Write() bufferedOutputStream->Write(kNew, sizeof(kNew) - 1, &rv); } + // All went ok. Maybe except for problems in Write(), but the stream detects + // that for us + nsCOMPtr safeStream = do_QueryInterface(fileOutputStream); + if (safeStream) + safeStream->SetWriteSucceeded(PR_TRUE); + mCookieChanged = PR_FALSE; return NS_OK; }