Thu, 15 Jan 2015 15:55:04 +0100
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 }