netwerk/cache/nsDeleteDir.cpp

Thu, 15 Jan 2015 15:55:04 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 15:55:04 +0100
branch
TOR_BUG_9701
changeset 9
a63d609f5ebe
permissions
-rw-r--r--

Back out 97036ab72558 which inappropriately compared turds to third parties.

     1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     2 /* vim:set ts=2 sw=2 sts=2 et cindent: */
     3 /* This Source Code Form is subject to the terms of the Mozilla Public
     4  * License, v. 2.0. If a copy of the MPL was not distributed with this
     5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     7 #include "nsDeleteDir.h"
     8 #include "nsIFile.h"
     9 #include "nsString.h"
    10 #include "mozilla/Telemetry.h"
    11 #include "nsITimer.h"
    12 #include "nsISimpleEnumerator.h"
    13 #include "nsAutoPtr.h"
    14 #include "nsThreadUtils.h"
    15 #include "nsISupportsPriority.h"
    16 #include "nsCacheUtils.h"
    17 #include "prtime.h"
    18 #include <time.h>
    20 using namespace mozilla;
    22 class nsBlockOnBackgroundThreadEvent : public nsRunnable {
    23 public:
    24   nsBlockOnBackgroundThreadEvent() {}
    25   NS_IMETHOD Run()
    26   {
    27     MutexAutoLock lock(nsDeleteDir::gInstance->mLock);
    28     nsDeleteDir::gInstance->mCondVar.Notify();
    29     return NS_OK;
    30   }
    31 };
    34 nsDeleteDir * nsDeleteDir::gInstance = nullptr;
    36 nsDeleteDir::nsDeleteDir()
    37   : mLock("nsDeleteDir.mLock"),
    38     mCondVar(mLock, "nsDeleteDir.mCondVar"),
    39     mShutdownPending(false),
    40     mStopDeleting(false)
    41 {
    42   NS_ASSERTION(gInstance==nullptr, "multiple nsCacheService instances!");
    43 }
    45 nsDeleteDir::~nsDeleteDir()
    46 {
    47   gInstance = nullptr;
    48 }
    50 nsresult
    51 nsDeleteDir::Init()
    52 {
    53   if (gInstance)
    54     return NS_ERROR_ALREADY_INITIALIZED;
    56   gInstance = new nsDeleteDir();
    57   return NS_OK;
    58 }
    60 nsresult
    61 nsDeleteDir::Shutdown(bool finishDeleting)
    62 {
    63   if (!gInstance)
    64     return NS_ERROR_NOT_INITIALIZED;
    66   nsCOMArray<nsIFile> dirsToRemove;
    67   nsCOMPtr<nsIThread> thread;
    68   {
    69     MutexAutoLock lock(gInstance->mLock);
    70     NS_ASSERTION(!gInstance->mShutdownPending,
    71                  "Unexpected state in nsDeleteDir::Shutdown()");
    72     gInstance->mShutdownPending = true;
    74     if (!finishDeleting)
    75       gInstance->mStopDeleting = true;
    77     // remove all pending timers
    78     for (int32_t i = gInstance->mTimers.Count(); i > 0; i--) {
    79       nsCOMPtr<nsITimer> timer = gInstance->mTimers[i-1];
    80       gInstance->mTimers.RemoveObjectAt(i-1);
    81       timer->Cancel();
    83       nsCOMArray<nsIFile> *arg;
    84       timer->GetClosure((reinterpret_cast<void**>(&arg)));
    86       if (finishDeleting)
    87         dirsToRemove.AppendObjects(*arg);
    89       // delete argument passed to the timer
    90       delete arg;
    91     }
    93     thread.swap(gInstance->mThread);
    94     if (thread) {
    95       // dispatch event and wait for it to run and notify us, so we know thread
    96       // has completed all work and can be shutdown
    97       nsCOMPtr<nsIRunnable> event = new nsBlockOnBackgroundThreadEvent();
    98       nsresult rv = thread->Dispatch(event, NS_DISPATCH_NORMAL);
    99       if (NS_FAILED(rv)) {
   100         NS_WARNING("Failed dispatching block-event");
   101         return NS_ERROR_UNEXPECTED;
   102       }
   104       rv = gInstance->mCondVar.Wait();
   105       nsShutdownThread::BlockingShutdown(thread);
   106     }
   107   }
   109   delete gInstance;
   111   for (int32_t i = 0; i < dirsToRemove.Count(); i++)
   112     dirsToRemove[i]->Remove(true);
   114   return NS_OK;
   115 }
   117 nsresult
   118 nsDeleteDir::InitThread()
   119 {
   120   if (mThread)
   121     return NS_OK;
   123   nsresult rv = NS_NewNamedThread("Cache Deleter", getter_AddRefs(mThread));
   124   if (NS_FAILED(rv)) {
   125     NS_WARNING("Can't create background thread");
   126     return rv;
   127   }
   129   nsCOMPtr<nsISupportsPriority> p = do_QueryInterface(mThread);
   130   if (p) {
   131     p->SetPriority(nsISupportsPriority::PRIORITY_LOWEST);
   132   }
   133   return NS_OK;
   134 }
   136 void
   137 nsDeleteDir::DestroyThread()
   138 {
   139   if (!mThread)
   140     return;
   142   if (mTimers.Count())
   143     // more work to do, so don't delete thread.
   144     return;
   146   nsShutdownThread::Shutdown(mThread);
   147   mThread = nullptr;
   148 }
   150 void
   151 nsDeleteDir::TimerCallback(nsITimer *aTimer, void *arg)
   152 {
   153   Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_DELETEDIR> timer;
   154   {
   155     MutexAutoLock lock(gInstance->mLock);
   157     int32_t idx = gInstance->mTimers.IndexOf(aTimer);
   158     if (idx == -1) {
   159       // Timer was canceled and removed during shutdown.
   160       return;
   161     }
   163     gInstance->mTimers.RemoveObjectAt(idx);
   164   }
   166   nsAutoPtr<nsCOMArray<nsIFile> > dirList;
   167   dirList = static_cast<nsCOMArray<nsIFile> *>(arg);
   169   bool shuttingDown = false;
   171   // Intentional extra braces to control variable sope.
   172   {
   173     // Low IO priority can only be set when running in the context of the
   174     // current thread.  So this shouldn't be moved to where we set the priority
   175     // of the Cache deleter thread using the nsThread's NSPR priority constants.
   176     nsAutoLowPriorityIO autoLowPriority;
   177     for (int32_t i = 0; i < dirList->Count() && !shuttingDown; i++) {
   178       gInstance->RemoveDir((*dirList)[i], &shuttingDown);
   179     }
   180   }
   182   {
   183     MutexAutoLock lock(gInstance->mLock);
   184     gInstance->DestroyThread();
   185   }
   186 }
   188 nsresult
   189 nsDeleteDir::DeleteDir(nsIFile *dirIn, bool moveToTrash, uint32_t delay)
   190 {
   191   Telemetry::AutoTimer<Telemetry::NETWORK_DISK_CACHE_TRASHRENAME> timer;
   193   if (!gInstance)
   194     return NS_ERROR_NOT_INITIALIZED;
   196   nsresult rv;
   197   nsCOMPtr<nsIFile> trash, dir;
   199   // Need to make a clone of this since we don't want to modify the input
   200   // file object.
   201   rv = dirIn->Clone(getter_AddRefs(dir));
   202   if (NS_FAILED(rv))
   203     return rv;
   205   if (moveToTrash) {
   206     rv = GetTrashDir(dir, &trash);
   207     if (NS_FAILED(rv))
   208       return rv;
   209     nsAutoCString origLeaf;
   210     rv = trash->GetNativeLeafName(origLeaf);
   211     if (NS_FAILED(rv))
   212       return rv;
   214     // Append random number to the trash directory and check if it exists.
   215     srand(static_cast<unsigned>(PR_Now()));
   216     nsAutoCString leaf;
   217     for (int32_t i = 0; i < 10; i++) {
   218       leaf = origLeaf;
   219       leaf.AppendInt(rand());
   220       rv = trash->SetNativeLeafName(leaf);
   221       if (NS_FAILED(rv))
   222         return rv;
   224       bool exists;
   225       if (NS_SUCCEEDED(trash->Exists(&exists)) && !exists) {
   226         break;
   227       }
   229       leaf.Truncate();
   230     }
   232     // Fail if we didn't find unused trash directory within the limit
   233     if (!leaf.Length())
   234       return NS_ERROR_FAILURE;
   236 #if defined(MOZ_WIDGET_ANDROID)
   237     nsCOMPtr<nsIFile> parent;
   238     rv = trash->GetParent(getter_AddRefs(parent));
   239     if (NS_FAILED(rv))
   240       return rv;
   241     rv = dir->MoveToNative(parent, leaf);
   242 #else
   243     // Important: must rename directory w/o changing parent directory: else on
   244     // NTFS we'll wait (with cache lock) while nsIFile's ACL reset walks file
   245     // tree: was hanging GUI for *minutes* on large cache dirs.
   246     rv = dir->MoveToNative(nullptr, leaf);
   247 #endif
   248     if (NS_FAILED(rv))
   249       return rv;
   250   } else {
   251     // we want to pass a clone of the original off to the worker thread.
   252     trash.swap(dir);
   253   }
   255   nsAutoPtr<nsCOMArray<nsIFile> > arg(new nsCOMArray<nsIFile>);
   256   arg->AppendObject(trash);
   258   rv = gInstance->PostTimer(arg, delay);
   259   if (NS_FAILED(rv))
   260     return rv;
   262   arg.forget();
   263   return NS_OK;
   264 }
   266 nsresult
   267 nsDeleteDir::GetTrashDir(nsIFile *target, nsCOMPtr<nsIFile> *result)
   268 {
   269   nsresult rv;
   270 #if defined(MOZ_WIDGET_ANDROID)
   271   // Try to use the app cache folder for cache trash on Android
   272   char* cachePath = getenv("CACHE_DIRECTORY");
   273   if (cachePath) {
   274     rv = NS_NewNativeLocalFile(nsDependentCString(cachePath),
   275                                true, getter_AddRefs(*result));
   276     if (NS_FAILED(rv))
   277       return rv;
   279     // Add a sub folder with the cache folder name
   280     nsAutoCString leaf;
   281     rv = target->GetNativeLeafName(leaf);
   282     (*result)->AppendNative(leaf);
   283   } else
   284 #endif
   285   {
   286     rv = target->Clone(getter_AddRefs(*result));
   287   }
   288   if (NS_FAILED(rv))
   289     return rv;
   291   nsAutoCString leaf;
   292   rv = (*result)->GetNativeLeafName(leaf);
   293   if (NS_FAILED(rv))
   294     return rv;
   295   leaf.AppendLiteral(".Trash");
   297   return (*result)->SetNativeLeafName(leaf);
   298 }
   300 nsresult
   301 nsDeleteDir::RemoveOldTrashes(nsIFile *cacheDir)
   302 {
   303   if (!gInstance)
   304     return NS_ERROR_NOT_INITIALIZED;
   306   nsresult rv;
   308   static bool firstRun = true;
   310   if (!firstRun)
   311     return NS_OK;
   313   firstRun = false;
   315   nsCOMPtr<nsIFile> trash;
   316   rv = GetTrashDir(cacheDir, &trash);
   317   if (NS_FAILED(rv))
   318     return rv;
   320   nsAutoString trashName;
   321   rv = trash->GetLeafName(trashName);
   322   if (NS_FAILED(rv))
   323     return rv;
   325   nsCOMPtr<nsIFile> parent;
   326 #if defined(MOZ_WIDGET_ANDROID)
   327   rv = trash->GetParent(getter_AddRefs(parent));
   328 #else
   329   rv = cacheDir->GetParent(getter_AddRefs(parent));
   330 #endif
   331   if (NS_FAILED(rv))
   332     return rv;
   334   nsCOMPtr<nsISimpleEnumerator> iter;
   335   rv = parent->GetDirectoryEntries(getter_AddRefs(iter));
   336   if (NS_FAILED(rv))
   337     return rv;
   339   bool more;
   340   nsCOMPtr<nsISupports> elem;
   341   nsAutoPtr<nsCOMArray<nsIFile> > dirList;
   343   while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
   344     rv = iter->GetNext(getter_AddRefs(elem));
   345     if (NS_FAILED(rv))
   346       continue;
   348     nsCOMPtr<nsIFile> file = do_QueryInterface(elem);
   349     if (!file)
   350       continue;
   352     nsAutoString leafName;
   353     rv = file->GetLeafName(leafName);
   354     if (NS_FAILED(rv))
   355       continue;
   357     // match all names that begin with the trash name (i.e. "Cache.Trash")
   358     if (Substring(leafName, 0, trashName.Length()).Equals(trashName)) {
   359       if (!dirList)
   360         dirList = new nsCOMArray<nsIFile>;
   361       dirList->AppendObject(file);
   362     }
   363   }
   365   if (dirList) {
   366     rv = gInstance->PostTimer(dirList, 90000);
   367     if (NS_FAILED(rv))
   368       return rv;
   370     dirList.forget();
   371   }
   373   return NS_OK;
   374 }
   376 nsresult
   377 nsDeleteDir::PostTimer(void *arg, uint32_t delay)
   378 {
   379   nsresult rv;
   381   nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1", &rv);
   382   if (NS_FAILED(rv))
   383     return NS_ERROR_UNEXPECTED;
   385   MutexAutoLock lock(mLock);
   387   rv = InitThread();
   388   if (NS_FAILED(rv))
   389     return rv;
   391   rv = timer->SetTarget(mThread);
   392   if (NS_FAILED(rv))
   393     return rv;
   395   rv = timer->InitWithFuncCallback(TimerCallback, arg, delay,
   396                                    nsITimer::TYPE_ONE_SHOT);
   397   if (NS_FAILED(rv))
   398     return rv;
   400   mTimers.AppendObject(timer);
   401   return NS_OK;
   402 }
   404 nsresult
   405 nsDeleteDir::RemoveDir(nsIFile *file, bool *stopDeleting)
   406 {
   407   nsresult rv;
   408   bool isLink;
   410   rv = file->IsSymlink(&isLink);
   411   if (NS_FAILED(rv) || isLink)
   412     return NS_ERROR_UNEXPECTED;
   414   bool isDir;
   415   rv = file->IsDirectory(&isDir);
   416   if (NS_FAILED(rv))
   417     return rv;
   419   if (isDir) {
   420     nsCOMPtr<nsISimpleEnumerator> iter;
   421     rv = file->GetDirectoryEntries(getter_AddRefs(iter));
   422     if (NS_FAILED(rv))
   423       return rv;
   425     bool more;
   426     nsCOMPtr<nsISupports> elem;
   427     while (NS_SUCCEEDED(iter->HasMoreElements(&more)) && more) {
   428       rv = iter->GetNext(getter_AddRefs(elem));
   429       if (NS_FAILED(rv)) {
   430         NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
   431         continue;
   432       }
   434       nsCOMPtr<nsIFile> file2 = do_QueryInterface(elem);
   435       if (!file2) {
   436         NS_WARNING("Unexpected failure in nsDeleteDir::RemoveDir");
   437         continue;
   438       }
   440       RemoveDir(file2, stopDeleting);
   441       // No check for errors to remove as much as possible
   443       if (*stopDeleting)
   444         return NS_OK;
   445     }
   446   }
   448   file->Remove(false);
   449   // No check for errors to remove as much as possible
   451   MutexAutoLock lock(mLock);
   452   if (mStopDeleting)
   453     *stopDeleting = true;
   455   return NS_OK;
   456 }

mercurial