123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428 |
- /* -*- 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/. */
- #include "nsDeleteDir.h"
- #include "nsIFile.h"
- #include "nsString.h"
- #include "mozilla/Telemetry.h"
- #include "nsITimer.h"
- #include "nsISimpleEnumerator.h"
- #include "nsAutoPtr.h"
- #include "nsThreadUtils.h"
- #include "nsISupportsPriority.h"
- #include "nsCacheUtils.h"
- #include "prtime.h"
- #include <time.h>
- using namespace mozilla;
- class nsBlockOnBackgroundThreadEvent : public Runnable {
- public:
- nsBlockOnBackgroundThreadEvent() {}
- NS_IMETHOD Run() override
- {
- MutexAutoLock lock(nsDeleteDir::gInstance->mLock);
- nsDeleteDir::gInstance->mNotified = true;
- nsDeleteDir::gInstance->mCondVar.Notify();
- return NS_OK;
- }
- };
- nsDeleteDir * nsDeleteDir::gInstance = nullptr;
- nsDeleteDir::nsDeleteDir()
- : mLock("nsDeleteDir.mLock"),
- mCondVar(mLock, "nsDeleteDir.mCondVar"),
- mNotified(false),
- mShutdownPending(false),
- mStopDeleting(false)
- {
- NS_ASSERTION(gInstance==nullptr, "multiple nsCacheService instances!");
- }
- nsDeleteDir::~nsDeleteDir()
- {
- gInstance = nullptr;
- }
- nsresult
- nsDeleteDir::Init()
- {
- if (gInstance)
- return NS_ERROR_ALREADY_INITIALIZED;
- gInstance = new nsDeleteDir();
- return NS_OK;
- }
- nsresult
- nsDeleteDir::Shutdown(bool finishDeleting)
- {
- if (!gInstance)
- return NS_ERROR_NOT_INITIALIZED;
- nsCOMArray<nsIFile> dirsToRemove;
- nsCOMPtr<nsIThread> thread;
- {
- MutexAutoLock lock(gInstance->mLock);
- NS_ASSERTION(!gInstance->mShutdownPending,
- "Unexpected state in nsDeleteDir::Shutdown()");
- gInstance->mShutdownPending = true;
- if (!finishDeleting)
- gInstance->mStopDeleting = true;
- // remove all pending timers
- for (int32_t i = gInstance->mTimers.Count(); i > 0; i--) {
- nsCOMPtr<nsITimer> timer = gInstance->mTimers[i-1];
- gInstance->mTimers.RemoveObjectAt(i-1);
- nsCOMArray<nsIFile> *arg;
- timer->GetClosure((reinterpret_cast<void**>(&arg)));
- timer->Cancel();
- if (finishDeleting)
- dirsToRemove.AppendObjects(*arg);
- // delete argument passed to the timer
- delete arg;
- }
- thread.swap(gInstance->mThread);
- if (thread) {
- // dispatch event and wait for it to run and notify us, so we know thread
- // has completed all work and can be shutdown
- nsCOMPtr<nsIRunnable> event = new nsBlockOnBackgroundThreadEvent();
- nsresult rv = thread->Dispatch(event, NS_DISPATCH_NORMAL);
- if (NS_FAILED(rv)) {
- NS_WARNING("Failed dispatching block-event");
- return NS_ERROR_UNEXPECTED;
- }
- gInstance->mNotified = false;
- while (!gInstance->mNotified) {
- gInstance->mCondVar.Wait();
- }
- nsShutdownThread::BlockingShutdown(thread);
- }
- }
- delete gInstance;
- for (int32_t i = 0; i < dirsToRemove.Count(); i++)
- dirsToRemove[i]->Remove(true);
- return NS_OK;
- }
- nsresult
- nsDeleteDir::InitThread()
- {
- if (mThread)
- return NS_OK;
- nsresult rv = NS_NewNamedThread("Cache Deleter", getter_AddRefs(mThread));
- if (NS_FAILED(rv)) {
- NS_WARNING("Can't create background thread");
- return rv;
- }
- nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mThread);
- if (p) {
- p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
- }
- return NS_OK;
- }
- void
- nsDeleteDir::DestroyThread()
- {
- if (!mThread)
- return;
- if (mTimers.Count())
- // more work to do, so don't delete thread.
- return;
- nsShutdownThread::Shutdown(mThread);
- mThread = nullptr;
- }
- void
- nsDeleteDir::TimerCallback(nsITimer *aTimer, void *arg)
- {
- {
- MutexAutoLock lock(gInstance->mLock);
- int32_t idx = gInstance->mTimers.IndexOf(aTimer);
- if (idx == -1) {
- // Timer was canceled and removed during shutdown.
- return;
- }
- gInstance->mTimers.RemoveObjectAt(idx);
- }
- nsAutoPtr<nsCOMArray<nsIFile> > dirList;
- dirList = static_cast<nsCOMArray<nsIFile> *>(arg);
- bool shuttingDown = false;
- // Intentional extra braces to control variable sope.
- {
- // Low IO priority can only be set when running in the context of the
- // current thread. So this shouldn't be moved to where we set the priority
- // of the Cache deleter thread using the nsThread's NSPR priority constants.
- nsAutoLowPriorityIO autoLowPriority;
- for (int32_t i = 0; i < dirList->Count() && !shuttingDown; i++) {
- gInstance->RemoveDir((*dirList)[i], &shuttingDown);
- }
- }
- {
- MutexAutoLock lock(gInstance->mLock);
- gInstance->DestroyThread();
- }
- }
- nsresult
- nsDeleteDir::DeleteDir(nsIFile *dirIn, bool moveToTrash, uint32_t delay)
- {
- if (!gInstance)
- return NS_ERROR_NOT_INITIALIZED;
- nsresult rv;
- nsCOMPtr<nsIFile> trash, dir;
- // Need to make a clone of this since we don't want to modify the input
- // file object.
- rv = dirIn->Clone(getter_AddRefs(dir));
- if (NS_FAILED(rv))
- return rv;
- if (moveToTrash) {
- rv = GetTrashDir(dir, &trash);
- if (NS_FAILED(rv))
- return rv;
- nsAutoCString origLeaf;
- rv = trash->GetNativeLeafName(origLeaf);
- if (NS_FAILED(rv))
- return rv;
- // Append random number to the trash directory and check if it exists.
- srand(static_cast<unsigned>(PR_Now()));
- nsAutoCString leaf;
- for (int32_t i = 0; i < 10; i++) {
- leaf = origLeaf;
- leaf.AppendInt(rand());
- rv = trash->SetNativeLeafName(leaf);
- if (NS_FAILED(rv))
- return rv;
- bool exists;
- if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
- break;
- }
- leaf.Truncate();
- }
- // Fail if we didn't find unused trash directory within the limit
- if (!leaf.Length())
- return NS_ERROR_FAILURE;
- // Important: must rename directory w/o changing parent directory: else on
- // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file
- // tree: was hanging GUI for *minutes* on large cache dirs.
- rv = dir->MoveToNative(nullptr, leaf);
- if (NS_FAILED(rv))
- return rv;
- } else {
- // we want to pass a clone of the original off to the worker thread.
- trash.swap(dir);
- }
- nsAutoPtr<nsCOMArray<nsIFile> > arg(new nsCOMArray<nsIFile>);
- arg->AppendObject(trash);
- rv = gInstance->PostTimer(arg, delay);
- if (NS_FAILED(rv))
- return rv;
- arg.forget();
- return NS_OK;
- }
- nsresult
- nsDeleteDir::GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result)
- {
- nsresult rv;
- {
- rv = target->Clone(getter_AddRefs(*result));
- }
- if (NS_FAILED(rv))
- return rv;
- nsAutoCString leaf;
- rv = (*result)->GetNativeLeafName(leaf);
- if (NS_FAILED(rv))
- return rv;
- leaf.AppendLiteral(".Trash");
- return (*result)->SetNativeLeafName(leaf);
- }
- nsresult
- nsDeleteDir::RemoveOldTrashes(nsIFile *cacheDir)
- {
- if (!gInstance)
- return NS_ERROR_NOT_INITIALIZED;
- nsresult rv;
- nsCOMPtr<nsIFile> trash;
- rv = GetTrashDir(cacheDir, &trash);
- if (NS_FAILED(rv))
- return rv;
- nsAutoString trashName;
- rv = trash->GetLeafName(trashName);
- if (NS_FAILED(rv))
- return rv;
- nsCOMPtr<nsIFile> parent;
- rv = cacheDir->GetParent(getter_AddRefs(parent));
- if (NS_FAILED(rv))
- return rv;
- nsCOMPtr<nsISimpleEnumerator> iter;
- rv = parent->GetDirectoryEntries(getter_AddRefs(iter));
- if (NS_FAILED(rv))
- return rv;
- bool more;
- nsCOMPtr<nsISupports> elem;
- nsAutoPtr<nsCOMArray<nsIFile> > dirList;
- while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
- rv = iter->GetNext(getter_AddRefs(elem));
- if (NS_FAILED(rv))
- continue;
- nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
- if (!file)
- continue;
- nsAutoString leafName;
- rv = file->GetLeafName(leafName);
- if (NS_FAILED(rv))
- continue;
- // match all names that begin with the trash name (i.e. "Cache.Trash")
- if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) {
- if (!dirList)
- dirList = new nsCOMArray<nsIFile>;
- dirList->AppendObject(file);
- }
- }
- if (dirList) {
- rv = gInstance->PostTimer(dirList, 90000);
- if (NS_FAILED(rv))
- return rv;
- dirList.forget();
- }
- return NS_OK;
- }
- nsresult
- nsDeleteDir::PostTimer(void *arg, uint32_t delay)
- {
- nsresult rv;
- nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
- if (NS_FAILED(rv))
- return NS_ERROR_UNEXPECTED;
- MutexAutoLock lock(mLock);
- rv = InitThread();
- if (NS_FAILED(rv))
- return rv;
- rv = timer->SetTarget(mThread);
- if (NS_FAILED(rv))
- return rv;
- rv = timer->InitWithFuncCallback(TimerCallback, arg, delay,
- nsITimer::TYPE_ONE_SHOT);
- if (NS_FAILED(rv))
- return rv;
- mTimers.AppendObject(timer);
- return NS_OK;
- }
- nsresult
- nsDeleteDir::RemoveDir(nsIFile *file, bool *stopDeleting)
- {
- nsresult rv;
- bool isLink;
- rv = file->IsSymlink(&isLink);
- if (NS_FAILED(rv) || isLink)
- return NS_ERROR_UNEXPECTED;
- bool isDir;
- rv = file->IsDirectory(&isDir);
- if (NS_FAILED(rv))
- return rv;
- if (isDir) {
- nsCOMPtr<nsISimpleEnumerator> iter;
- rv = file->GetDirectoryEntries(getter_AddRefs(iter));
- if (NS_FAILED(rv))
- return rv;
- bool more;
- nsCOMPtr<nsISupports> elem;
- while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
- rv = iter->GetNext(getter_AddRefs(elem));
- if (NS_FAILED(rv)) {
- NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
- continue;
- }
- nsCOMPtr<nsIFile> file2 = do_QueryInterface(elem);
- if (!file2) {
- NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
- continue;
- }
- RemoveDir(file2, stopDeleting);
- // No check for errors to remove as much as possible
- if (*stopDeleting)
- return NS_OK;
- }
- }
- file->Remove(false);
- // No check for errors to remove as much as possible
- MutexAutoLock lock(mLock);
- if (mStopDeleting)
- *stopDeleting = true;
- return NS_OK;
- }
|