michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: michael@0: #include "nsAnonymousTemporaryFile.h" michael@0: #include "nsDirectoryServiceUtils.h" michael@0: #include "nsDirectoryServiceDefs.h" michael@0: #include "nsXULAppAPI.h" michael@0: #include "nsCOMPtr.h" michael@0: #include "nsString.h" michael@0: #include "nsAppDirectoryServiceDefs.h" michael@0: michael@0: #ifdef XP_WIN michael@0: #include "nsIObserver.h" michael@0: #include "nsIObserverService.h" michael@0: #include "mozilla/Services.h" michael@0: #include "nsIIdleService.h" michael@0: #include "nsISimpleEnumerator.h" michael@0: #include "nsIFile.h" michael@0: #include "nsAutoPtr.h" michael@0: #include "nsITimer.h" michael@0: #include "nsCRT.h" michael@0: michael@0: using namespace mozilla; michael@0: #endif michael@0: michael@0: michael@0: // We store the temp files in the system temp dir. michael@0: // michael@0: // On Windows systems in particular we use a sub-directory of the temp michael@0: // directory, because: michael@0: // 1. DELETE_ON_CLOSE is unreliable on Windows, in particular if we power michael@0: // cycle (and perhaps if we crash) the files are not deleted. We store michael@0: // the temporary files in a known sub-dir so that we can find and delete michael@0: // them easily and quickly. michael@0: // 2. On Windows NT the system temp dir is in the user's $HomeDir/AppData, michael@0: // so we can be sure the user always has write privileges to that directory; michael@0: // if the sub-dir for our temp files was in some shared location and michael@0: // was created by a privileged user, it's possible that other users michael@0: // wouldn't have write access to that sub-dir. (Non-Windows systems michael@0: // don't store their temp files in a sub-dir, so this isn't an issue on michael@0: // those platforms). michael@0: // 3. Content processes can access the system temp dir michael@0: // (NS_GetSpecialDirectory fails on NS_APP_USER_PROFILE_LOCAL_50_DIR michael@0: // for content process for example, which is where we previously stored michael@0: // temp files on Windows). This argument applies to all platforms, not michael@0: // just Windows. michael@0: static nsresult michael@0: GetTempDir(nsIFile** aTempDir) michael@0: { michael@0: if (NS_WARN_IF(!aTempDir)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: nsCOMPtr tmpFile; michael@0: nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: #ifdef XP_WIN michael@0: // On windows DELETE_ON_CLOSE is unreliable, so we store temporary files michael@0: // in a subdir of the temp dir and delete that in an idle service observer michael@0: // to ensure it's been cleared. michael@0: rv = tmpFile->AppendNative(nsDependentCString("mozilla-temp-files")); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); michael@0: if (rv != NS_ERROR_FILE_ALREADY_EXISTS && NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: #endif michael@0: michael@0: tmpFile.forget(aTempDir); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc) michael@0: { michael@0: if (NS_WARN_IF(!aOutFileDesc)) michael@0: return NS_ERROR_INVALID_ARG; michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr tmpFile; michael@0: rv = GetTempDir(getter_AddRefs(tmpFile)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Give the temp file a name with a random element. CreateUnique will also michael@0: // append a counter to the name if it encounters a name collision. Adding michael@0: // a random element to the name reduces the likelihood of a name collision, michael@0: // so that CreateUnique() doesn't end up trying a lot of name variants in michael@0: // its "try appending an incrementing counter" loop, as file IO can be michael@0: // expensive on some mobile flash drives. michael@0: nsAutoCString name("mozilla-temp-"); michael@0: name.AppendInt(rand()); michael@0: michael@0: rv = tmpFile->AppendNative(name); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE, michael@0: PR_IRWXU, aOutFileDesc); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: #ifdef XP_WIN michael@0: michael@0: // On Windows we have an idle service observer that runs some time after michael@0: // startup and deletes any stray anonymous temporary files... michael@0: michael@0: // Duration of idle time before we'll get a callback whereupon we attempt to michael@0: // remove any stray and unused anonymous temp files. michael@0: #define TEMP_FILE_IDLE_TIME_S 30 michael@0: michael@0: // The nsAnonTempFileRemover is created in a timer, which sets an idle observer. michael@0: // This is expiration time (in ms) which initial timer is set for (3 minutes). michael@0: #define SCHEDULE_TIMEOUT_MS 3 * 60 * 1000 michael@0: michael@0: #define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown" michael@0: michael@0: // This class adds itself as an idle observer. When the application has michael@0: // been idle for about 30 seconds we'll get a notification, whereupon we'll michael@0: // attempt to delete ${TempDir}/mozilla-temp-files/. This is to ensure all michael@0: // temp files that were supposed to be deleted on application exit were actually michael@0: // deleted, as they may not be if we previously crashed. See bugs 572579 and michael@0: // 785662. This is only needed on some versions of Windows, michael@0: // nsIFile::DELETE_ON_CLOSE works on other platforms. michael@0: // This class adds itself as a shutdown observer so that it can cancel the michael@0: // idle observer and its timer on shutdown. Note: the observer and idle michael@0: // services hold references to instances of this object, and those references michael@0: // are what keep this object alive. michael@0: class nsAnonTempFileRemover MOZ_FINAL : public nsIObserver { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: nsAnonTempFileRemover() { michael@0: MOZ_COUNT_CTOR(nsAnonTempFileRemover); michael@0: } michael@0: michael@0: ~nsAnonTempFileRemover() { michael@0: MOZ_COUNT_DTOR(nsAnonTempFileRemover); michael@0: } michael@0: michael@0: nsresult Init() { michael@0: // We add the idle observer in a timer, so that the app has enough michael@0: // time to start up before we add the idle observer. If we register the michael@0: // idle observer too early, it will be registered before the fake idle michael@0: // service is installed when running in xpcshell, and this interferes with michael@0: // the fake idle service, causing xpcshell-test failures. michael@0: mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); michael@0: if (NS_WARN_IF(!mTimer)) michael@0: return NS_ERROR_FAILURE; michael@0: nsresult rv = mTimer->Init(this, michael@0: SCHEDULE_TIMEOUT_MS, michael@0: nsITimer::TYPE_ONE_SHOT); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return rv; michael@0: michael@0: // Register shutdown observer so we can cancel the timer if we shutdown before michael@0: // the timer runs. michael@0: nsCOMPtr obsSrv = services::GetObserverService(); michael@0: if (NS_WARN_IF(!obsSrv)) michael@0: return NS_ERROR_FAILURE; michael@0: return obsSrv->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false); michael@0: } michael@0: michael@0: void Cleanup() { michael@0: // Cancel timer. michael@0: if (mTimer) { michael@0: mTimer->Cancel(); michael@0: mTimer = nullptr; michael@0: } michael@0: // Remove idle service observer. michael@0: nsCOMPtr idleSvc = michael@0: do_GetService("@mozilla.org/widget/idleservice;1"); michael@0: if (idleSvc) { michael@0: idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME_S); michael@0: } michael@0: // Remove shutdown observer. michael@0: nsCOMPtr obsSrv = services::GetObserverService(); michael@0: if (obsSrv) { michael@0: obsSrv->RemoveObserver(this, XPCOM_SHUTDOWN_TOPIC); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP Observe(nsISupports *aSubject, michael@0: const char *aTopic, michael@0: const char16_t *aData) michael@0: { michael@0: if (nsCRT::strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0 && michael@0: NS_FAILED(RegisterIdleObserver())) { michael@0: Cleanup(); michael@0: } else if (nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0) { michael@0: // The user has been idle for a while, clean up the temp files. michael@0: // The idle service will drop its reference to this object after michael@0: // we exit, destroying this object. michael@0: RemoveAnonTempFileFiles(); michael@0: Cleanup(); michael@0: } else if (nsCRT::strcmp(aTopic, XPCOM_SHUTDOWN_TOPIC) == 0) { michael@0: Cleanup(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult RegisterIdleObserver() { michael@0: // Add this as an idle observer. When we've been idle for michael@0: // TEMP_FILE_IDLE_TIME_S seconds, we'll get a notification, and we'll then michael@0: // try to delete any stray temp files. michael@0: nsCOMPtr idleSvc = michael@0: do_GetService("@mozilla.org/widget/idleservice;1"); michael@0: if (!idleSvc) michael@0: return NS_ERROR_FAILURE; michael@0: return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME_S); michael@0: } michael@0: michael@0: void RemoveAnonTempFileFiles() { michael@0: nsCOMPtr tmpDir; michael@0: nsresult rv = GetTempDir(getter_AddRefs(tmpDir)); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) michael@0: return; michael@0: michael@0: // Remove the directory recursively. michael@0: tmpDir->Remove(true); michael@0: } michael@0: michael@0: private: michael@0: nsCOMPtr mTimer; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsAnonTempFileRemover, nsIObserver) michael@0: michael@0: nsresult CreateAnonTempFileRemover() { michael@0: // Create a temp file remover. If Init() succeeds, the temp file remover is kept michael@0: // alive by a reference held by the observer service, since the temp file remover michael@0: // is a shutdown observer. We only create the temp file remover if we're running michael@0: // in the main process; there's no point in doing the temp file removal multiple michael@0: // times per startup. michael@0: if (XRE_GetProcessType() != GeckoProcessType_Default) { michael@0: return NS_OK; michael@0: } michael@0: nsRefPtr tempRemover = new nsAnonTempFileRemover(); michael@0: return tempRemover->Init(); michael@0: } michael@0: michael@0: #endif michael@0: