LCOV - code coverage report
Current view: top level - netwerk/cache - nsDeleteDir.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 69 226 30.5 %
Date: 2017-07-14 16:53:18 Functions: 5 16 31.2 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13