From d7f1641d0da09edb4ec3142c7205d8f18ad6d2cc Mon Sep 17 00:00:00 2001 From: Chris Pearce Date: Tue, 25 Sep 2012 12:50:30 +1200 Subject: [PATCH] Bug 785662 - Add NS_OpenAnonymousTemporaryFile(), and use it in nsMediaCache::Init(). r=roc --HG-- rename : xpcom/io/nsMediaCacheRemover.cpp => xpcom/io/nsAnonymousTemporaryFile.cpp --- content/media/nsMediaCache.cpp | 45 +---- xpcom/build/nsXPComInit.cpp | 4 +- xpcom/io/Makefile.in | 4 +- xpcom/io/nsAnonymousTemporaryFile.cpp | 240 ++++++++++++++++++++++++++ xpcom/io/nsAnonymousTemporaryFile.h | 31 ++++ xpcom/io/nsIFile.idl | 17 +- xpcom/io/nsMediaCacheRemover.cpp | 118 ------------- 7 files changed, 291 insertions(+), 168 deletions(-) create mode 100644 xpcom/io/nsAnonymousTemporaryFile.cpp create mode 100644 xpcom/io/nsAnonymousTemporaryFile.h delete mode 100644 xpcom/io/nsMediaCacheRemover.cpp diff --git a/content/media/nsMediaCache.cpp b/content/media/nsMediaCache.cpp index 0b4aee659b79..a6a09219b631 100644 --- a/content/media/nsMediaCache.cpp +++ b/content/media/nsMediaCache.cpp @@ -8,9 +8,6 @@ #include "mozilla/XPCOM.h" #include "nsMediaCache.h" -#include "nsDirectoryServiceUtils.h" -#include "nsDirectoryServiceDefs.h" -#include "nsXULAppAPI.h" #include "nsNetUtil.h" #include "prio.h" #include "nsContentUtils.h" @@ -21,6 +18,7 @@ #include "mozilla/Preferences.h" #include "FileBlockCache.h" #include "mozilla/Attributes.h" +#include "nsAnonymousTemporaryFile.h" using namespace mozilla; @@ -519,47 +517,8 @@ nsMediaCache::Init() NS_ASSERTION(NS_IsMainThread(), "Only call on main thread"); NS_ASSERTION(!mFileCache, "Cache file already open?"); - // In single process Gecko, store the media cache in the profile directory - // so that multiple users can use separate media caches concurrently. - // In multi-process Gecko, there is no profile dir, so just store it in the - // system temp directory instead. - nsresult rv; - nsCOMPtr tmpFile; - const char* dir = (XRE_GetProcessType() == GeckoProcessType_Content) ? - NS_OS_TEMP_DIR : NS_APP_USER_PROFILE_LOCAL_50_DIR; - rv = NS_GetSpecialDirectory(dir, getter_AddRefs(tmpFile)); - NS_ENSURE_SUCCESS(rv,rv); - - // We put the media cache file in - // ${TempDir}/mozilla-media-cache/media_cache - rv = tmpFile->AppendNative(nsDependentCString("mozilla-media-cache")); - NS_ENSURE_SUCCESS(rv,rv); - - rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); - if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { - // Ensure the permissions are 0700. If not, we won't be able to create, - // read to and write from the media cache file in its subdirectory on - // non-Windows platforms. - uint32_t perms; - rv = tmpFile->GetPermissions(&perms); - NS_ENSURE_SUCCESS(rv,rv); - if (perms != 0700) { - rv = tmpFile->SetPermissions(0700); - NS_ENSURE_SUCCESS(rv,rv); - } - } else { - NS_ENSURE_SUCCESS(rv,rv); - } - - rv = tmpFile->AppendNative(nsDependentCString("media_cache")); - NS_ENSURE_SUCCESS(rv,rv); - - rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700); - NS_ENSURE_SUCCESS(rv,rv); - PRFileDesc* fileDesc = nullptr; - rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE, - PR_IRWXU, &fileDesc); + nsresult rv = NS_OpenAnonymousTemporaryFile(&fileDesc); NS_ENSURE_SUCCESS(rv,rv); mFileCache = new FileBlockCache(); diff --git a/xpcom/build/nsXPComInit.cpp b/xpcom/build/nsXPComInit.cpp index 3c135c2838a3..05b05e386e13 100644 --- a/xpcom/build/nsXPComInit.cpp +++ b/xpcom/build/nsXPComInit.cpp @@ -141,7 +141,7 @@ extern nsresult NS_RegistryGetFactory(nsIFactory** aFactory); extern nsresult NS_CategoryManagerGetFactory( nsIFactory** ); #ifdef XP_WIN -extern nsresult ScheduleMediaCacheRemover(); +extern nsresult CreateAnonTempFileRemover(); #endif NS_GENERIC_FACTORY_CONSTRUCTOR(nsProcess) @@ -464,7 +464,7 @@ NS_InitXPCOM2(nsIServiceManager* *result, nullptr, NS_XPCOM_STARTUP_OBSERVER_ID); #ifdef XP_WIN - ScheduleMediaCacheRemover(); + CreateAnonTempFileRemover(); #endif mozilla::MapsMemoryReporter::Init(); diff --git a/xpcom/io/Makefile.in b/xpcom/io/Makefile.in index d840c49f071e..307afc23b00a 100644 --- a/xpcom/io/Makefile.in +++ b/xpcom/io/Makefile.in @@ -26,6 +26,7 @@ endif CPPSRCS = \ Base64.cpp \ + nsAnonymousTemporaryFile.cpp \ nsAppFileLocationProvider.cpp \ nsBinaryStream.cpp \ nsDirectoryService.cpp \ @@ -58,13 +59,14 @@ ifeq ($(MOZ_WIDGET_TOOLKIT),os2) CPPSRCS += nsLocalFileOS2.cpp else ifeq ($(MOZ_WIDGET_TOOLKIT),windows) -CPPSRCS += nsLocalFileWin.cpp nsMediaCacheRemover.cpp +CPPSRCS += nsLocalFileWin.cpp else CPPSRCS += nsLocalFileUnix.cpp endif # windows endif # OS2 EXPORTS = \ + nsAnonymousTemporaryFile.h \ nsAppDirectoryServiceDefs.h \ nsDirectoryService.h \ nsDirectoryServiceAtomList.h \ diff --git a/xpcom/io/nsAnonymousTemporaryFile.cpp b/xpcom/io/nsAnonymousTemporaryFile.cpp new file mode 100644 index 000000000000..dec1e06249e5 --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.cpp @@ -0,0 +1,240 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "nsAnonymousTemporaryFile.h" +#include "nsDirectoryServiceUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "nsXULAppAPI.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsAppDirectoryServiceDefs.h" + +#ifdef XP_WIN +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "mozilla/Services.h" +#include "nsIIdleService.h" +#include "nsISimpleEnumerator.h" +#include "nsIFile.h" +#include "nsAutoPtr.h" +#include "nsITimer.h" +#include "nsCRT.h" + +using namespace mozilla; +#endif + + +// We store the temp files in the system temp dir. +// +// On Windows systems in particular we use a sub-directory of the temp +// directory, because: +// 1. DELETE_ON_CLOSE is unreliable on Windows, in particular if we power +// cycle (and perhaps if we crash) the files are not deleted. We store +// the temporary files in a known sub-dir so that we can find and delete +// them easily and quickly. +// 2. On Windows NT the system temp dir is in the user's $HomeDir/AppData, +// so we can be sure the user always has write privileges to that directory; +// if the sub-dir for our temp files was in some shared location and +// was created by a privileged user, it's possible that other users +// wouldn't have write access to that sub-dir. (Non-Windows systems +// don't store their temp files in a sub-dir, so this isn't an issue on +// those platforms). +// 3. Content processes can access the system temp dir +// (NS_GetSpecialDirectory fails on NS_APP_USER_PROFILE_LOCAL_50_DIR +// for content process for example, which is where we previously stored +// temp files on Windows). This argument applies to all platforms, not +// just Windows. +static nsresult +GetTempDir(nsIFile** aTempDir) +{ + NS_ENSURE_ARG(aTempDir); + nsCOMPtr tmpFile; + nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv,rv); + +#ifdef XP_WIN + // On windows DELETE_ON_CLOSE is unreliable, so we store temporary files + // in a subdir of the temp dir and delete that in an idle service observer + // to ensure it's been cleared. + rv = tmpFile->AppendNative(nsDependentCString("mozilla-temp-files")); + NS_ENSURE_SUCCESS(rv,rv); + rv = tmpFile->Create(nsIFile::DIRECTORY_TYPE, 0700); + NS_ENSURE_TRUE(rv == NS_ERROR_FILE_ALREADY_EXISTS || NS_SUCCEEDED(rv), rv); +#endif + + tmpFile.forget(aTempDir); + + return NS_OK; +} + +nsresult +NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc) +{ + NS_ENSURE_ARG(aOutFileDesc); + nsresult rv; + + nsCOMPtr tmpFile; + rv = GetTempDir(getter_AddRefs(tmpFile)); + NS_ENSURE_SUCCESS(rv,rv); + + // Give the temp file a name with a random element. CreateUnique will also + // append a counter to the name if it encounters a name collision. Adding + // a random element to the name reduces the likelihood of a name collision, + // so that CreateUnique() doesn't end up trying a lot of name variants in + // its "try appending an incrementing counter" loop, as file IO can be + // expensive on some mobile flash drives. + nsAutoCString name("mozilla-temp-"); + name.AppendInt(rand()); + + rv = tmpFile->AppendNative(name); + NS_ENSURE_SUCCESS(rv,rv); + + rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700); + NS_ENSURE_SUCCESS(rv,rv); + + rv = tmpFile->OpenNSPRFileDesc(PR_RDWR | nsIFile::DELETE_ON_CLOSE, + PR_IRWXU, aOutFileDesc); + + return rv; +} + +#ifdef XP_WIN + +// On Windows we have an idle service observer that runs some time after +// startup and deletes any stray anonymous temporary files... + +// Duration of idle time before we'll get a callback whereupon we attempt to +// remove any stray and unused anonymous temp files. +#define TEMP_FILE_IDLE_TIME_S 30 + +// The nsAnonTempFileRemover is created in a timer, which sets an idle observer. +// This is expiration time (in ms) which initial timer is set for (3 minutes). +#define SCHEDULE_TIMEOUT_MS 3 * 60 * 1000 + +#define XPCOM_SHUTDOWN_TOPIC "xpcom-shutdown" + +// This class adds itself as an idle observer. When the application has +// been idle for about 30 seconds we'll get a notification, whereupon we'll +// attempt to delete ${TempDir}/mozilla-temp-files/. This is to ensure all +// temp files that were supposed to be deleted on application exit were actually +// deleted, as they may not be if we previously crashed. See bugs 572579 and +// 785662. This is only needed on some versions of Windows, +// nsIFile::DELETE_ON_CLOSE works on other platforms. +// This class adds itself as a shutdown observer so that it can cancel the +// idle observer and its timer on shutdown. Note: the observer and idle +// services hold references to instances of this object, and those references +// are what keep this object alive. +class nsAnonTempFileRemover : public nsIObserver { +public: + NS_DECL_ISUPPORTS + + nsAnonTempFileRemover() { + MOZ_COUNT_CTOR(nsAnonTempFileRemover); + } + + ~nsAnonTempFileRemover() { + MOZ_COUNT_DTOR(nsAnonTempFileRemover); + } + + nsresult Init() { + // We add the idle observer in a timer, so that the app has enough + // time to start up before we add the idle observer. If we register the + // idle observer too early, it will be registered before the fake idle + // service is installed when running in xpcshell, and this interferes with + // the fake idle service, causing xpcshell-test failures. + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + NS_ENSURE_TRUE(mTimer != nullptr, NS_ERROR_FAILURE); + nsresult rv = mTimer->Init(this, + SCHEDULE_TIMEOUT_MS, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, rv); + + // Register shutdown observer so we can cancel the timer if we shutdown before + // the timer runs. + nsCOMPtr obsSrv = services::GetObserverService(); + NS_ENSURE_TRUE(obsSrv != nullptr, NS_ERROR_FAILURE); + return obsSrv->AddObserver(this, XPCOM_SHUTDOWN_TOPIC, false); + } + + void Cleanup() { + // Cancel timer. + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + // Remove idle service observer. + nsCOMPtr idleSvc = + do_GetService("@mozilla.org/widget/idleservice;1"); + if (idleSvc) { + idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME_S); + } + // Remove shutdown observer. + nsCOMPtr obsSrv = services::GetObserverService(); + if (obsSrv) { + obsSrv->RemoveObserver(this, XPCOM_SHUTDOWN_TOPIC); + } + } + + NS_IMETHODIMP Observe(nsISupports *aSubject, + const char *aTopic, + const PRUnichar *aData) + { + if (nsCRT::strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0 && + NS_FAILED(RegisterIdleObserver())) { + Cleanup(); + } else if (nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE) == 0) { + // The user has been idle for a while, clean up the temp files. + // The idle service will drop its reference to this object after + // we exit, destroying this object. + RemoveAnonTempFileFiles(); + Cleanup(); + } else if (nsCRT::strcmp(aTopic, XPCOM_SHUTDOWN_TOPIC) == 0) { + Cleanup(); + } + return NS_OK; + } + + nsresult RegisterIdleObserver() { + // Add this as an idle observer. When we've been idle for + // TEMP_FILE_IDLE_TIME_S seconds, we'll get a notification, and we'll then + // try to delete any stray temp files. + nsCOMPtr idleSvc = + do_GetService("@mozilla.org/widget/idleservice;1"); + if (!idleSvc) + return NS_ERROR_FAILURE; + return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME_S); + } + + void RemoveAnonTempFileFiles() { + nsCOMPtr tmpDir; + nsresult rv = GetTempDir(getter_AddRefs(tmpDir)); + NS_ENSURE_SUCCESS(rv,); + + // Remove the directory recursively. + tmpDir->Remove(true); + } + +private: + nsCOMPtr mTimer; +}; + +NS_IMPL_ISUPPORTS1(nsAnonTempFileRemover, nsIObserver) + +nsresult CreateAnonTempFileRemover() { + // Create a temp file remover. If Init() succeeds, the temp file remover is kept + // alive by a reference held by the observer service, since the temp file remover + // is a shutdown observer. We only create the temp file remover if we're running + // in the main process; there's no point in doing the temp file removal multiple + // times per startup. + if (XRE_GetProcessType() != GeckoProcessType_Default) { + return NS_OK; + } + nsRefPtr tempRemover = new nsAnonTempFileRemover(); + return tempRemover->Init(); +} + +#endif + diff --git a/xpcom/io/nsAnonymousTemporaryFile.h b/xpcom/io/nsAnonymousTemporaryFile.h new file mode 100644 index 000000000000..2a0968a41614 --- /dev/null +++ b/xpcom/io/nsAnonymousTemporaryFile.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#pragma once + +#include "prio.h" +#include "nscore.h" + +/** + * OpenAnonymousTemporaryFile + * + * Creates and opens a temporary file which has a random name. Callers have no + * control over the file name, and the file is opened in a temporary location + * which is appropriate for the platform. + * + * Upon success, aOutFileDesc contains an opened handle to the temporary file. + * The caller is responsible for closing the file when they're finished with it. + * + * The file will be deleted when the file handle is closed. On non-Windows + * platforms the file will be unlinked before this function returns. On Windows + * the OS supplied delete-on-close mechanism is unreliable if the application + * crashes or the computer power cycles unexpectedly, so unopened temporary + * files are purged at some time after application startup. + * + */ +nsresult +NS_OpenAnonymousTemporaryFile(PRFileDesc** aOutFileDesc); + diff --git a/xpcom/io/nsIFile.idl b/xpcom/io/nsIFile.idl index a9b5cb7c085a..a3338723e991 100644 --- a/xpcom/io/nsIFile.idl +++ b/xpcom/io/nsIFile.idl @@ -344,7 +344,17 @@ interface nsIFile : nsISupports */ attribute boolean followLinks; + /** + * Flag for openNSPRFileDesc(), to hint to the OS that the file will be + * read sequentially with agressive readahead. + */ const unsigned long OS_READAHEAD = 0x40000000; + + /** + * Flag for openNSPRFileDesc(). Deprecated and unreliable! + * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary + * file which will be deleted upon close! + */ const unsigned long DELETE_ON_CLOSE = 0x80000000; /** @@ -354,10 +364,9 @@ interface nsIFile : nsISupports * @param flags the PR_Open flags from prio.h, plus optionally * OS_READAHEAD or DELETE_ON_CLOSE. OS_READAHEAD is a hint to the * OS that the file will be read sequentially with agressive - * readahead. DELETE_ON_CLOSE may be implemented by removing the - * file (by path name) immediately after opening it, so beware of - * possible races; the file should be exclusively owned by this - * process. + * readahead. DELETE_ON_CLOSE is unreliable on Windows and is deprecated. + * Instead use NS_OpenAnonymousTemporaryFile() to create a temporary + * file which will be deleted upon close. */ [noscript] PRFileDescStar openNSPRFileDesc(in long flags, in long mode); diff --git a/xpcom/io/nsMediaCacheRemover.cpp b/xpcom/io/nsMediaCacheRemover.cpp deleted file mode 100644 index 7af01694299c..000000000000 --- a/xpcom/io/nsMediaCacheRemover.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -#ifndef XP_WIN -#error nsMediaCacheRemover only needed on Windows. -#endif - -#include "nsIObserver.h" -#include "nsIIdleService.h" -#include "nsISimpleEnumerator.h" -#include "nsIFile.h" -#include "nsAppDirectoryServiceDefs.h" -#include "nsDirectoryServiceDefs.h" -#include "nsXULAppAPI.h" -#include "nsString.h" -#include "nsAutoPtr.h" -#include "nsITimer.h" - -// Duration of idle time before we'll get a callback whereupon we attempt to -// remove any stray and unused media cache temp files. -#define TEMP_FILE_IDLE_TIME 30 - -// The nsMediaCacheRemover is created in a timer, which sets an idle observer. -// This is expiration time (in ms) which initial timer is set for (3 minutes). -#define SCHEDULE_TIMEOUT 3 * 60 * 1000 - -// This class adds itself as an idle observer. When the application has -// been idle for about 30 seconds we'll get a notification, whereupon we'll -// attempt to delete ${TempDir}/mozilla-media-cache/. This is to ensure all -// media cache temp files which were supposed to be deleted on application -// exit were actually deleted as they may not be if we previously crashed. -// See bug 572579. This is only needed on some versions of Windows, -// nsIFile::DELETE_ON_CLOSE works on other platforms. -class nsMediaCacheRemover : public nsIObserver { -public: - NS_DECL_ISUPPORTS - - nsMediaCacheRemover() { - MOZ_COUNT_CTOR(nsMediaCacheRemover); - } - - ~nsMediaCacheRemover() { - MOZ_COUNT_DTOR(nsMediaCacheRemover); - } - - NS_IMETHODIMP Observe(nsISupports *subject, - const char *topic, - const PRUnichar *data) - { - if (strcmp(topic, "idle") == 0) { - // The user has been idle for a while, clean up the temp files. - // The idle service will drop its reference to this object after - // we exit, destroying this object. - RemoveMediaCacheFiles(); - } - return NS_OK; - } - - nsresult RegisterIdleObserver() { - // Add this as an idle observer. When we've been idle for - // TEMP_FILE_IDLE_TIME seconds, we'll get a notification, and we'll then - // try to delete any stray media cache temp files. - nsCOMPtr idleSvc = - do_GetService("@mozilla.org/widget/idleservice;1"); - if (!idleSvc) - return NS_ERROR_FAILURE; - return idleSvc->AddIdleObserver(this, TEMP_FILE_IDLE_TIME); - } - - void RemoveMediaCacheFiles() { - nsCOMPtr idleSvc = - do_GetService("@mozilla.org/widget/idleservice;1"); - if (idleSvc) - idleSvc->RemoveIdleObserver(this, TEMP_FILE_IDLE_TIME); - - nsCOMPtr tmpDir; - nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR, - getter_AddRefs(tmpDir)); - if (NS_FAILED(rv)) - return; - - NS_ABORT_IF_FALSE(XRE_GetProcessType() == GeckoProcessType_Default, - "Need to update media cache file location"); - - rv = tmpDir->AppendNative(nsDependentCString("mozilla-media-cache")); - if (NS_FAILED(rv)) - return; - - // Remove the directory recursively. - tmpDir->Remove(true); - } -}; - -NS_IMPL_ISUPPORTS1(nsMediaCacheRemover, nsIObserver) - -void CreateMediaCacheRemover(nsITimer* aTimer, void* aClosure) { - // Create a new nsMediaCacheRemover, and register it as an idle observer. - // If it is successfully registered as an idle observer, its owning reference - // will be held by the idle service, otherwise it will be destroyed by the - // refptr here when it goes out of scope. - nsRefPtr t = new nsMediaCacheRemover(); - t->RegisterIdleObserver(); -} - -nsresult ScheduleMediaCacheRemover() { - // We create the nsMediaCacheRemover in a timer, so that the app has enough - // time to start up before we add the idle observer. If we register the - // idle observer too early, it will be registered before the fake idle - // service is installed when running in xpcshell, and this interferes with - // the fake idle service, causing xpcshell-test failures. - nsCOMPtr t = do_CreateInstance(NS_TIMER_CONTRACTID); - nsresult res = t->InitWithFuncCallback(CreateMediaCacheRemover, - 0, - SCHEDULE_TIMEOUT, - nsITimer::TYPE_ONE_SHOT); - return res; -}