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