1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
4 * You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "nsIAppStartup.h"
7 #include "nsIFile.h"
8 #include "nsIStringBundle.h"
9 #include "nsIToolkitProfile.h"
10 #include "nsIWindowWatcher.h"
11
12 #include "ProfileReset.h"
13
14 #include "nsDirectoryServiceDefs.h"
15 #include "nsDirectoryServiceUtils.h"
16 #include "nsPIDOMWindow.h"
17 #include "nsPrintfCString.h"
18 #include "nsString.h"
19 #include "nsToolkitCompsCID.h"
20 #include "nsXPCOMCIDInternal.h"
21 #include "mozilla/XREAppData.h"
22
23 #include "mozilla/Services.h"
24 #include "prtime.h"
25
26 using namespace mozilla;
27
28 extern const XREAppData* gAppData;
29
30 static const char kProfileProperties[] =
31 "chrome://mozapps/locale/profile/profileSelection.properties";
32
33 /**
34 * Creates a new profile with a timestamp in the name to use for profile reset.
35 */
CreateResetProfile(nsIToolkitProfileService * aProfileSvc,const nsACString & aOldProfileName,nsIToolkitProfile ** aNewProfile)36 nsresult CreateResetProfile(nsIToolkitProfileService* aProfileSvc,
37 const nsACString& aOldProfileName,
38 nsIToolkitProfile** aNewProfile) {
39 MOZ_ASSERT(aProfileSvc, "NULL profile service");
40
41 nsCOMPtr<nsIToolkitProfile> newProfile;
42 // Make the new profile the old profile (or "default-") + the time in seconds
43 // since epoch for uniqueness.
44 nsAutoCString newProfileName;
45 if (!aOldProfileName.IsEmpty()) {
46 newProfileName.Assign(aOldProfileName);
47 newProfileName.Append("-");
48 } else {
49 newProfileName.AssignLiteral("default-");
50 }
51 newProfileName.Append(nsPrintfCString("%" PRId64, PR_Now() / 1000));
52 nsresult rv =
53 aProfileSvc->CreateProfile(nullptr, // choose a default dir for us
54 newProfileName, getter_AddRefs(newProfile));
55 if (NS_FAILED(rv)) return rv;
56
57 rv = aProfileSvc->Flush();
58 if (NS_FAILED(rv)) return rv;
59
60 newProfile.swap(*aNewProfile);
61
62 return NS_OK;
63 }
64
65 /**
66 * Delete the profile directory being reset after a backup and delete the local
67 * profile directory.
68 */
ProfileResetCleanup(nsIToolkitProfile * aOldProfile)69 nsresult ProfileResetCleanup(nsIToolkitProfile* aOldProfile) {
70 nsresult rv;
71 nsCOMPtr<nsIFile> profileDir;
72 rv = aOldProfile->GetRootDir(getter_AddRefs(profileDir));
73 if (NS_FAILED(rv)) return rv;
74
75 nsCOMPtr<nsIFile> profileLocalDir;
76 rv = aOldProfile->GetLocalDir(getter_AddRefs(profileLocalDir));
77 if (NS_FAILED(rv)) return rv;
78
79 // Get the friendly name for the backup directory.
80 nsCOMPtr<nsIStringBundleService> sbs =
81 mozilla::services::GetStringBundleService();
82 if (!sbs) return NS_ERROR_FAILURE;
83
84 nsCOMPtr<nsIStringBundle> sb;
85 rv = sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb));
86 if (!sb) return NS_ERROR_FAILURE;
87
88 NS_ConvertUTF8toUTF16 appName(gAppData->name);
89 const char16_t* params[] = {appName.get(), appName.get()};
90
91 nsAutoString resetBackupDirectoryName;
92
93 static const char* kResetBackupDirectory = "resetBackupDirectory";
94 rv = sb->FormatStringFromName(kResetBackupDirectory, params, 2,
95 resetBackupDirectoryName);
96
97 // Get info to copy the old root profile dir to the desktop as a backup.
98 nsCOMPtr<nsIFile> backupDest, containerDest, profileDest;
99 rv = NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(backupDest));
100 if (NS_FAILED(rv)) {
101 // Fall back to the home directory if the desktop is not available.
102 rv = NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(backupDest));
103 if (NS_FAILED(rv)) return rv;
104 }
105
106 // Try to create a directory for all the backups
107 backupDest->Clone(getter_AddRefs(containerDest));
108 containerDest->Append(resetBackupDirectoryName);
109 rv = containerDest->Create(nsIFile::DIRECTORY_TYPE, 0700);
110 // It's OK if it already exists, if and only if it is a directory
111 if (rv == NS_ERROR_FILE_ALREADY_EXISTS) {
112 bool containerIsDir;
113 rv = containerDest->IsDirectory(&containerIsDir);
114 if (NS_FAILED(rv) || !containerIsDir) {
115 return rv;
116 }
117 } else if (NS_FAILED(rv)) {
118 return rv;
119 }
120
121 // Get the name of the profile
122 nsAutoString leafName;
123 rv = profileDir->GetLeafName(leafName);
124 if (NS_FAILED(rv)) return rv;
125
126 // Try to create a unique directory for the profile:
127 containerDest->Clone(getter_AddRefs(profileDest));
128 profileDest->Append(leafName);
129 rv = profileDest->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700);
130 if (NS_FAILED(rv)) return rv;
131
132 // Get the unique profile name
133 rv = profileDest->GetLeafName(leafName);
134 if (NS_FAILED(rv)) return rv;
135
136 // Delete the empty directory that CreateUnique just created.
137 rv = profileDest->Remove(false);
138 if (NS_FAILED(rv)) return rv;
139
140 // Show a progress window while the cleanup happens since the disk I/O can
141 // take time.
142 nsCOMPtr<nsIWindowWatcher> windowWatcher(
143 do_GetService(NS_WINDOWWATCHER_CONTRACTID));
144 if (!windowWatcher) return NS_ERROR_FAILURE;
145
146 nsCOMPtr<nsIAppStartup> appStartup(do_GetService(NS_APPSTARTUP_CONTRACTID));
147 if (!appStartup) return NS_ERROR_FAILURE;
148
149 nsCOMPtr<mozIDOMWindowProxy> progressWindow;
150 rv = windowWatcher->OpenWindow(nullptr, kResetProgressURL, "_blank",
151 "centerscreen,chrome,titlebar", nullptr,
152 getter_AddRefs(progressWindow));
153 if (NS_FAILED(rv)) return rv;
154
155 // Create a new thread to do the bulk of profile cleanup to stay responsive.
156 nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID);
157 nsCOMPtr<nsIThread> cleanupThread;
158 rv = tm->NewThread(0, 0, getter_AddRefs(cleanupThread));
159 if (NS_SUCCEEDED(rv)) {
160 nsCOMPtr<nsIRunnable> runnable = new ProfileResetCleanupAsyncTask(
161 profileDir, profileLocalDir, containerDest, leafName);
162 cleanupThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL);
163 // The result callback will shut down the worker thread.
164
165 // Wait for the cleanup thread to complete.
166 SpinEventLoopUntil([&]() { return gProfileResetCleanupCompleted; });
167 } else {
168 gProfileResetCleanupCompleted = true;
169 NS_WARNING("Cleanup thread creation failed");
170 return rv;
171 }
172 // Close the progress window now that the cleanup thread is done.
173 auto* piWindow = nsPIDOMWindowOuter::From(progressWindow);
174 piWindow->Close();
175
176 // Delete the old profile from profiles.ini. The folder was already deleted by
177 // the thread above.
178 rv = aOldProfile->Remove(false);
179 if (NS_FAILED(rv)) NS_WARNING("Could not remove the profile");
180
181 return rv;
182 }
183