|
4 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | 5 |
|
6 | 6 | #include "mozilla/ArrayUtils.h" |
| 7 | +#include "mozilla/ScopeExit.h" |
7 | 8 | #include "mozilla/UniquePtr.h" |
8 | 9 | #include "mozilla/UniquePtrExtensions.h" |
9 | 10 | #include "mozilla/WidgetUtils.h" |
| 11 | +#include "nsProfileLock.h" |
10 | 12 |
|
11 | 13 | #include <stdio.h> |
12 | 14 | #include <stdlib.h> |
|
50 | 52 | #include "nsIToolkitShellService.h" |
51 | 53 | #include "mozilla/Telemetry.h" |
52 | 54 | #include "nsProxyRelease.h" |
| 55 | +#include "prinrval.h" |
| 56 | +#include "prthread.h" |
53 | 57 |
|
54 | 58 | using namespace mozilla; |
55 | 59 |
|
@@ -86,41 +90,130 @@ nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser, |
86 | 90 | return result; |
87 | 91 | } |
88 | 92 |
|
| 93 | +void RemoveProfileRecursion(const nsCOMPtr<nsIFile>& aDirectoryOrFile, |
| 94 | + bool aIsIgnoreRoot, bool aIsIgnoreLockfile, |
| 95 | + nsTArray<nsCOMPtr<nsIFile>>& aOutUndeletedFiles) { |
| 96 | + auto guardDeletion = MakeScopeExit( |
| 97 | + [&] { aOutUndeletedFiles.AppendElement(aDirectoryOrFile); }); |
| 98 | + |
| 99 | + // We actually would not expect to see links in our profiles, but still. |
| 100 | + bool isLink = false; |
| 101 | + NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsSymlink(&isLink)); |
| 102 | + |
| 103 | + // Only check to see if we have a directory if it isn't a link. |
| 104 | + bool isDir = false; |
| 105 | + if (!isLink) { |
| 106 | + NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsDirectory(&isDir)); |
| 107 | + } |
| 108 | + |
| 109 | + if (isDir) { |
| 110 | + nsCOMPtr<nsIDirectoryEnumerator> dirEnum; |
| 111 | + NS_ENSURE_SUCCESS_VOID( |
| 112 | + aDirectoryOrFile->GetDirectoryEntries(getter_AddRefs(dirEnum))); |
| 113 | + |
| 114 | + bool more = false; |
| 115 | + while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) { |
| 116 | + nsCOMPtr<nsISupports> item; |
| 117 | + dirEnum->GetNext(getter_AddRefs(item)); |
| 118 | + nsCOMPtr<nsIFile> file = do_QueryInterface(item); |
| 119 | + if (file) { |
| 120 | + // Do not delete the profile lock. |
| 121 | + if (aIsIgnoreLockfile && nsProfileLock::IsMaybeLockFile(file)) continue; |
| 122 | + // If some children's remove fails, we still continue the loop. |
| 123 | + RemoveProfileRecursion(file, false, false, aOutUndeletedFiles); |
| 124 | + } |
| 125 | + } |
| 126 | + } |
| 127 | + // Do not delete the root directory (yet). |
| 128 | + if (!aIsIgnoreRoot) { |
| 129 | + NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->Remove(false)); |
| 130 | + } |
| 131 | + guardDeletion.release(); |
| 132 | +} |
| 133 | + |
89 | 134 | void RemoveProfileFiles(nsIToolkitProfile* aProfile, bool aInBackground) { |
90 | 135 | nsCOMPtr<nsIFile> rootDir; |
91 | 136 | aProfile->GetRootDir(getter_AddRefs(rootDir)); |
92 | 137 | nsCOMPtr<nsIFile> localDir; |
93 | 138 | aProfile->GetLocalDir(getter_AddRefs(localDir)); |
94 | 139 |
|
| 140 | + // XXX If we get here with an active quota manager, |
| 141 | + // something went very wrong. We want to assert this. |
| 142 | + |
95 | 143 | // Just lock the directories, don't mark the profile as locked or the lock |
96 | 144 | // will attempt to release its reference to the profile on the background |
97 | 145 | // thread which will assert. |
98 | 146 | nsCOMPtr<nsIProfileLock> lock; |
99 | | - nsresult rv = |
100 | | - NS_LockProfilePath(rootDir, localDir, nullptr, getter_AddRefs(lock)); |
101 | | - NS_ENSURE_SUCCESS_VOID(rv); |
| 147 | + NS_ENSURE_SUCCESS_VOID( |
| 148 | + NS_LockProfilePath(rootDir, localDir, nullptr, getter_AddRefs(lock))); |
102 | 149 |
|
103 | 150 | nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( |
104 | 151 | "nsToolkitProfile::RemoveProfileFiles", |
105 | 152 | [rootDir, localDir, lock]() mutable { |
| 153 | + // We try to remove every single file and directory and collect |
| 154 | + // those whose removal failed. |
| 155 | + nsTArray<nsCOMPtr<nsIFile>> undeletedFiles; |
| 156 | + // The root dir might contain the temp dir, so remove the temp dir |
| 157 | + // first. |
106 | 158 | bool equals; |
107 | 159 | nsresult rv = rootDir->Equals(localDir, &equals); |
108 | | - // The root dir might contain the temp dir, so remove |
109 | | - // the temp dir first. |
110 | 160 | if (NS_SUCCEEDED(rv) && !equals) { |
111 | | - localDir->Remove(true); |
| 161 | + RemoveProfileRecursion(localDir, |
| 162 | + /* aIsIgnoreRoot */ false, |
| 163 | + /* aIsIgnoreLockfile */ false, undeletedFiles); |
112 | 164 | } |
| 165 | + // Now remove the content of the profile dir (except lockfile) |
| 166 | + RemoveProfileRecursion(rootDir, |
| 167 | + /* aIsIgnoreRoot */ true, |
| 168 | + /* aIsIgnoreLockfile */ true, undeletedFiles); |
| 169 | + |
| 170 | + // Retry loop if something was not deleted |
| 171 | + if (undeletedFiles.Length() > 0) { |
| 172 | + uint32_t retries = 1; |
| 173 | + // XXX: Until bug 1716291 is fixed we just make one retry |
| 174 | + while (undeletedFiles.Length() > 0 && retries <= 1) { |
| 175 | + Unused << PR_Sleep(PR_MillisecondsToInterval(10 * retries)); |
| 176 | + for (auto&& file : |
| 177 | + std::exchange(undeletedFiles, nsTArray<nsCOMPtr<nsIFile>>{})) { |
| 178 | + RemoveProfileRecursion(file, |
| 179 | + /* aIsIgnoreRoot */ false, |
| 180 | + /* aIsIgnoreLockfile */ true, |
| 181 | + undeletedFiles); |
| 182 | + } |
| 183 | + retries++; |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | +#ifdef DEBUG |
| 188 | + // XXX: Until bug 1716291 is fixed, we do not want to spam release |
| 189 | + if (undeletedFiles.Length() > 0) { |
| 190 | + NS_WARNING("Unable to remove all files from the profile directory:"); |
| 191 | + // Log the file names of those we could not remove |
| 192 | + for (auto&& file : undeletedFiles) { |
| 193 | + nsAutoString leafName; |
| 194 | + if (NS_SUCCEEDED(file->GetLeafName(leafName))) { |
| 195 | + NS_WARNING(NS_LossyConvertUTF16toASCII(leafName).get()); |
| 196 | + } |
| 197 | + } |
| 198 | + } |
| 199 | +#endif |
| 200 | + // XXX: Activate this assert once bug 1716291 is fixed |
| 201 | + // MOZ_ASSERT(undeletedFiles.Length() == 0); |
113 | 202 |
|
114 | | - // Ideally we'd unlock after deleting but since the lock is a file |
115 | | - // in the profile we must unlock before removing. |
| 203 | + // Now we can unlock the profile safely. |
116 | 204 | lock->Unlock(); |
117 | 205 | // nsIProfileLock is not threadsafe so release our reference to it on |
118 | 206 | // the main thread. |
119 | 207 | NS_ReleaseOnMainThread("nsToolkitProfile::RemoveProfileFiles::Unlock", |
120 | 208 | lock.forget()); |
121 | 209 |
|
122 | | - rv = rootDir->Remove(true); |
123 | | - NS_ENSURE_SUCCESS_VOID(rv); |
| 210 | + if (undeletedFiles.Length() == 0) { |
| 211 | + // We can safely remove the (empty) remaining profile directory |
| 212 | + // and lockfile, no other files are here. |
| 213 | + // As we do this only if we had no other blockers, this is as safe |
| 214 | + // as deleting the lockfile explicitely after unlocking. |
| 215 | + Unused << rootDir->Remove(true); |
| 216 | + } |
124 | 217 | }); |
125 | 218 |
|
126 | 219 | if (aInBackground) { |
@@ -350,6 +443,9 @@ nsToolkitProfileLock::Unlock() { |
350 | 443 | return NS_ERROR_UNEXPECTED; |
351 | 444 | } |
352 | 445 |
|
| 446 | + // XXX If we get here with an active quota manager, |
| 447 | + // something went very wrong. We want to assert this. |
| 448 | + |
353 | 449 | mLock.Unlock(); |
354 | 450 |
|
355 | 451 | if (mProfile) { |
|
0 commit comments