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 : }
|