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.

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

mercurial