Skip to content

Commit fc2b0ab

Browse files
committed
Bug 1714573: Ensure profile deletion takes place before the profile is unlocked. r=mossop,dom-storage-reviewers,janv
We want to make our best effort to delete all files we can before we unlock the profile. This includes a retry loop hoping that whoever else locked those files did so intermittently. And once the profile is unlocked, we want to reduce as much as possible the possibility to interfere with a starting instance on the same profile directory. Until bug 1716291 lands, we need to get back here to finish the work, but this patch is already supposed to mitigate some of the problems we might see in the wild. Differential Revision: https://phabricator.services.mozilla.com/D116834
1 parent 30825d8 commit fc2b0ab

File tree

4 files changed

+133
-17
lines changed

4 files changed

+133
-17
lines changed

toolkit/profile/nsProfileLock.cpp

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -399,18 +399,29 @@ nsresult nsProfileLock::GetReplacedLockTime(PRTime* aResult) {
399399
return NS_OK;
400400
}
401401

402-
nsresult nsProfileLock::Lock(nsIFile* aProfileDir,
403-
nsIProfileUnlocker** aUnlocker) {
404402
#if defined(XP_MACOSX)
405-
constexpr auto LOCKFILE_NAME = u".parentlock"_ns;
406-
constexpr auto OLD_LOCKFILE_NAME = u"parent.lock"_ns;
403+
constexpr auto LOCKFILE_NAME = u".parentlock"_ns;
404+
constexpr auto OLD_LOCKFILE_NAME = u"parent.lock"_ns;
407405
#elif defined(XP_UNIX)
408-
constexpr auto OLD_LOCKFILE_NAME = u"lock"_ns;
409-
constexpr auto LOCKFILE_NAME = u".parentlock"_ns;
406+
constexpr auto OLD_LOCKFILE_NAME = u"lock"_ns;
407+
constexpr auto LOCKFILE_NAME = u".parentlock"_ns;
410408
#else
411-
constexpr auto LOCKFILE_NAME = u"parent.lock"_ns;
409+
constexpr auto LOCKFILE_NAME = u"parent.lock"_ns;
410+
#endif
411+
412+
bool nsProfileLock::IsMaybeLockFile(nsIFile* aFile) {
413+
nsAutoString tmp;
414+
if (NS_SUCCEEDED(aFile->GetLeafName(tmp))) {
415+
if (tmp.Equals(LOCKFILE_NAME)) return true;
416+
#if (defined(XP_MACOSX) || defined(XP_UNIX))
417+
if (tmp.Equals(OLD_LOCKFILE_NAME)) return true;
412418
#endif
419+
}
420+
return false;
421+
}
413422

423+
nsresult nsProfileLock::Lock(nsIFile* aProfileDir,
424+
nsIProfileUnlocker** aUnlocker) {
414425
nsresult rv;
415426
if (aUnlocker) *aUnlocker = nullptr;
416427

toolkit/profile/nsProfileLock.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ class nsProfileLock
5050
*/
5151
nsresult Unlock(bool aFatalSignal = false);
5252

53+
/**
54+
* Checks, if the given file has a name that matches potential lock file
55+
* names. It does not check, if it actually is the currently active lock.
56+
*/
57+
static bool IsMaybeLockFile(nsIFile* aFile);
58+
5359
/**
5460
* Clean up any left over files in the directory.
5561
*/

toolkit/profile/nsToolkitProfileService.cpp

Lines changed: 106 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
55

66
#include "mozilla/ArrayUtils.h"
7+
#include "mozilla/ScopeExit.h"
78
#include "mozilla/UniquePtr.h"
89
#include "mozilla/UniquePtrExtensions.h"
910
#include "mozilla/WidgetUtils.h"
11+
#include "nsProfileLock.h"
1012

1113
#include <stdio.h>
1214
#include <stdlib.h>
@@ -50,6 +52,8 @@
5052
#include "nsIToolkitShellService.h"
5153
#include "mozilla/Telemetry.h"
5254
#include "nsProxyRelease.h"
55+
#include "prinrval.h"
56+
#include "prthread.h"
5357

5458
using namespace mozilla;
5559

@@ -86,41 +90,130 @@ nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
8690
return result;
8791
}
8892

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+
89134
void RemoveProfileFiles(nsIToolkitProfile* aProfile, bool aInBackground) {
90135
nsCOMPtr<nsIFile> rootDir;
91136
aProfile->GetRootDir(getter_AddRefs(rootDir));
92137
nsCOMPtr<nsIFile> localDir;
93138
aProfile->GetLocalDir(getter_AddRefs(localDir));
94139

140+
// XXX If we get here with an active quota manager,
141+
// something went very wrong. We want to assert this.
142+
95143
// Just lock the directories, don't mark the profile as locked or the lock
96144
// will attempt to release its reference to the profile on the background
97145
// thread which will assert.
98146
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)));
102149

103150
nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
104151
"nsToolkitProfile::RemoveProfileFiles",
105152
[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.
106158
bool equals;
107159
nsresult rv = rootDir->Equals(localDir, &equals);
108-
// The root dir might contain the temp dir, so remove
109-
// the temp dir first.
110160
if (NS_SUCCEEDED(rv) && !equals) {
111-
localDir->Remove(true);
161+
RemoveProfileRecursion(localDir,
162+
/* aIsIgnoreRoot */ false,
163+
/* aIsIgnoreLockfile */ false, undeletedFiles);
112164
}
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);
113202

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.
116204
lock->Unlock();
117205
// nsIProfileLock is not threadsafe so release our reference to it on
118206
// the main thread.
119207
NS_ReleaseOnMainThread("nsToolkitProfile::RemoveProfileFiles::Unlock",
120208
lock.forget());
121209

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+
}
124217
});
125218

126219
if (aInBackground) {
@@ -350,6 +443,9 @@ nsToolkitProfileLock::Unlock() {
350443
return NS_ERROR_UNEXPECTED;
351444
}
352445

446+
// XXX If we get here with an active quota manager,
447+
// something went very wrong. We want to assert this.
448+
353449
mLock.Unlock();
354450

355451
if (mProfile) {

toolkit/profile/test/test_create_profile.xhtml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ function createProfile(profileName) {
106106

107107
// Clean up the profile before local profile test.
108108
profile.remove(true);
109+
ok(!jsonFile.exists(), "Times file was removed");
110+
ok(!profileDir.exists(), "Profile dir was removed");
109111

110112
// Create with non-null aRootDir
111113
profile = gProfileService.createProfile(profileDir, profileName);
@@ -116,6 +118,7 @@ function createProfile(profileName) {
116118

117119
// Clean up the profile.
118120
profile.remove(true);
121+
ok(!profileDir.exists(), "Profile dir was removed");
119122
}
120123

121124
]]>

0 commit comments

Comments
 (0)