1 /* -*- Mode: C++; tab-width: 8; 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
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5
6 #include "mozilla/ArrayUtils.h"
7 #include "mozilla/ScopeExit.h"
8 #include "mozilla/UniquePtr.h"
9 #include "mozilla/UniquePtrExtensions.h"
10 #include "mozilla/WidgetUtils.h"
11 #include "nsProfileLock.h"
12
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <prprf.h>
16 #include <prtime.h>
17
18 #ifdef XP_WIN
19 # include <windows.h>
20 # include <shlobj.h>
21 # include "mozilla/PolicyChecks.h"
22 #endif
23 #ifdef XP_UNIX
24 # include <unistd.h>
25 #endif
26
27 #include "nsToolkitProfileService.h"
28 #include "CmdLineAndEnvUtils.h"
29 #include "nsIFile.h"
30
31 #ifdef XP_MACOSX
32 # include <CoreFoundation/CoreFoundation.h>
33 # include "nsILocalFileMac.h"
34 #endif
35
36 #ifdef MOZ_WIDGET_GTK
37 # include "mozilla/WidgetUtilsGtk.h"
38 #endif
39
40 #include "nsAppDirectoryServiceDefs.h"
41 #include "nsDirectoryServiceDefs.h"
42 #include "nsNetCID.h"
43 #include "nsXULAppAPI.h"
44 #include "nsThreadUtils.h"
45
46 #include "nsIRunnable.h"
47 #include "nsXREDirProvider.h"
48 #include "nsAppRunner.h"
49 #include "nsString.h"
50 #include "nsReadableUtils.h"
51 #include "nsNativeCharsetUtils.h"
52 #include "mozilla/Attributes.h"
53 #include "mozilla/Sprintf.h"
54 #include "nsPrintfCString.h"
55 #include "mozilla/UniquePtr.h"
56 #include "nsIToolkitShellService.h"
57 #include "mozilla/Telemetry.h"
58 #include "nsProxyRelease.h"
59 #include "prinrval.h"
60 #include "prthread.h"
61
62 using namespace mozilla;
63
64 #define DEV_EDITION_NAME "dev-edition-default"
65 #define DEFAULT_NAME "default"
66 #define COMPAT_FILE u"compatibility.ini"_ns
67 #define PROFILE_DB_VERSION "2"
68 #define INSTALL_PREFIX "Install"
69 #define INSTALL_PREFIX_LENGTH 7
70
71 struct KeyValue {
KeyValueKeyValue72 KeyValue(const char* aKey, const char* aValue) : key(aKey), value(aValue) {}
73
74 nsCString key;
75 nsCString value;
76 };
77
GetStrings(const char * aString,const char * aValue,void * aClosure)78 static bool GetStrings(const char* aString, const char* aValue,
79 void* aClosure) {
80 nsTArray<UniquePtr<KeyValue>>* array =
81 static_cast<nsTArray<UniquePtr<KeyValue>>*>(aClosure);
82 array->AppendElement(MakeUnique<KeyValue>(aString, aValue));
83
84 return true;
85 }
86
87 /**
88 * Returns an array of the strings inside a section of an ini file.
89 */
GetSectionStrings(nsINIParser * aParser,const char * aSection)90 nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
91 const char* aSection) {
92 nsTArray<UniquePtr<KeyValue>> result;
93 aParser->GetStrings(aSection, &GetStrings, &result);
94 return result;
95 }
96
RemoveProfileRecursion(const nsCOMPtr<nsIFile> & aDirectoryOrFile,bool aIsIgnoreRoot,bool aIsIgnoreLockfile,nsTArray<nsCOMPtr<nsIFile>> & aOutUndeletedFiles)97 void RemoveProfileRecursion(const nsCOMPtr<nsIFile>& aDirectoryOrFile,
98 bool aIsIgnoreRoot, bool aIsIgnoreLockfile,
99 nsTArray<nsCOMPtr<nsIFile>>& aOutUndeletedFiles) {
100 auto guardDeletion = MakeScopeExit(
101 [&] { aOutUndeletedFiles.AppendElement(aDirectoryOrFile); });
102
103 // We actually would not expect to see links in our profiles, but still.
104 bool isLink = false;
105 NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsSymlink(&isLink));
106
107 // Only check to see if we have a directory if it isn't a link.
108 bool isDir = false;
109 if (!isLink) {
110 NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsDirectory(&isDir));
111 }
112
113 if (isDir) {
114 nsCOMPtr<nsIDirectoryEnumerator> dirEnum;
115 NS_ENSURE_SUCCESS_VOID(
116 aDirectoryOrFile->GetDirectoryEntries(getter_AddRefs(dirEnum)));
117
118 bool more = false;
119 while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) {
120 nsCOMPtr<nsISupports> item;
121 dirEnum->GetNext(getter_AddRefs(item));
122 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
123 if (file) {
124 // Do not delete the profile lock.
125 if (aIsIgnoreLockfile && nsProfileLock::IsMaybeLockFile(file)) continue;
126 // If some children's remove fails, we still continue the loop.
127 RemoveProfileRecursion(file, false, false, aOutUndeletedFiles);
128 }
129 }
130 }
131 // Do not delete the root directory (yet).
132 if (!aIsIgnoreRoot) {
133 NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->Remove(false));
134 }
135 guardDeletion.release();
136 }
137
RemoveProfileFiles(nsIToolkitProfile * aProfile,bool aInBackground)138 void RemoveProfileFiles(nsIToolkitProfile* aProfile, bool aInBackground) {
139 nsCOMPtr<nsIFile> rootDir;
140 aProfile->GetRootDir(getter_AddRefs(rootDir));
141 nsCOMPtr<nsIFile> localDir;
142 aProfile->GetLocalDir(getter_AddRefs(localDir));
143
144 // XXX If we get here with an active quota manager,
145 // something went very wrong. We want to assert this.
146
147 // Just lock the directories, don't mark the profile as locked or the lock
148 // will attempt to release its reference to the profile on the background
149 // thread which will assert.
150 nsCOMPtr<nsIProfileLock> lock;
151 NS_ENSURE_SUCCESS_VOID(
152 NS_LockProfilePath(rootDir, localDir, nullptr, getter_AddRefs(lock)));
153
154 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
155 "nsToolkitProfile::RemoveProfileFiles",
156 [rootDir, localDir, lock]() mutable {
157 // We try to remove every single file and directory and collect
158 // those whose removal failed.
159 nsTArray<nsCOMPtr<nsIFile>> undeletedFiles;
160 // The root dir might contain the temp dir, so remove the temp dir
161 // first.
162 bool equals;
163 nsresult rv = rootDir->Equals(localDir, &equals);
164 if (NS_SUCCEEDED(rv) && !equals) {
165 RemoveProfileRecursion(localDir,
166 /* aIsIgnoreRoot */ false,
167 /* aIsIgnoreLockfile */ false, undeletedFiles);
168 }
169 // Now remove the content of the profile dir (except lockfile)
170 RemoveProfileRecursion(rootDir,
171 /* aIsIgnoreRoot */ true,
172 /* aIsIgnoreLockfile */ true, undeletedFiles);
173
174 // Retry loop if something was not deleted
175 if (undeletedFiles.Length() > 0) {
176 uint32_t retries = 1;
177 // XXX: Until bug 1716291 is fixed we just make one retry
178 while (undeletedFiles.Length() > 0 && retries <= 1) {
179 Unused << PR_Sleep(PR_MillisecondsToInterval(10 * retries));
180 for (auto&& file :
181 std::exchange(undeletedFiles, nsTArray<nsCOMPtr<nsIFile>>{})) {
182 RemoveProfileRecursion(file,
183 /* aIsIgnoreRoot */ false,
184 /* aIsIgnoreLockfile */ true,
185 undeletedFiles);
186 }
187 retries++;
188 }
189 }
190
191 #ifdef DEBUG
192 // XXX: Until bug 1716291 is fixed, we do not want to spam release
193 if (undeletedFiles.Length() > 0) {
194 NS_WARNING("Unable to remove all files from the profile directory:");
195 // Log the file names of those we could not remove
196 for (auto&& file : undeletedFiles) {
197 nsAutoString leafName;
198 if (NS_SUCCEEDED(file->GetLeafName(leafName))) {
199 NS_WARNING(NS_LossyConvertUTF16toASCII(leafName).get());
200 }
201 }
202 }
203 #endif
204 // XXX: Activate this assert once bug 1716291 is fixed
205 // MOZ_ASSERT(undeletedFiles.Length() == 0);
206
207 // Now we can unlock the profile safely.
208 lock->Unlock();
209 // nsIProfileLock is not threadsafe so release our reference to it on
210 // the main thread.
211 NS_ReleaseOnMainThread("nsToolkitProfile::RemoveProfileFiles::Unlock",
212 lock.forget());
213
214 if (undeletedFiles.Length() == 0) {
215 // We can safely remove the (empty) remaining profile directory
216 // and lockfile, no other files are here.
217 // As we do this only if we had no other blockers, this is as safe
218 // as deleting the lockfile explicitely after unlocking.
219 Unused << rootDir->Remove(true);
220 }
221 });
222
223 if (aInBackground) {
224 nsCOMPtr<nsIEventTarget> target =
225 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
226 target->Dispatch(runnable, NS_DISPATCH_NORMAL);
227 } else {
228 runnable->Run();
229 }
230 }
231
nsToolkitProfile(const nsACString & aName,nsIFile * aRootDir,nsIFile * aLocalDir,bool aFromDB)232 nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
233 nsIFile* aLocalDir, bool aFromDB)
234 : mName(aName),
235 mRootDir(aRootDir),
236 mLocalDir(aLocalDir),
237 mLock(nullptr),
238 mIndex(0),
239 mSection("Profile") {
240 NS_ASSERTION(aRootDir, "No file!");
241
242 RefPtr<nsToolkitProfile> prev =
243 nsToolkitProfileService::gService->mProfiles.getLast();
244 if (prev) {
245 mIndex = prev->mIndex + 1;
246 }
247 mSection.AppendInt(mIndex);
248
249 nsToolkitProfileService::gService->mProfiles.insertBack(this);
250
251 // If this profile isn't in the database already add it.
252 if (!aFromDB) {
253 nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
254 db->SetString(mSection.get(), "Name", mName.get());
255
256 bool isRelative = false;
257 nsCString descriptor;
258 nsToolkitProfileService::gService->GetProfileDescriptor(this, descriptor,
259 &isRelative);
260
261 db->SetString(mSection.get(), "IsRelative", isRelative ? "1" : "0");
262 db->SetString(mSection.get(), "Path", descriptor.get());
263 }
264 }
265
NS_IMPL_ISUPPORTS(nsToolkitProfile,nsIToolkitProfile)266 NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)
267
268 NS_IMETHODIMP
269 nsToolkitProfile::GetRootDir(nsIFile** aResult) {
270 NS_ADDREF(*aResult = mRootDir);
271 return NS_OK;
272 }
273
274 NS_IMETHODIMP
GetLocalDir(nsIFile ** aResult)275 nsToolkitProfile::GetLocalDir(nsIFile** aResult) {
276 NS_ADDREF(*aResult = mLocalDir);
277 return NS_OK;
278 }
279
280 NS_IMETHODIMP
GetName(nsACString & aResult)281 nsToolkitProfile::GetName(nsACString& aResult) {
282 aResult = mName;
283 return NS_OK;
284 }
285
286 NS_IMETHODIMP
SetName(const nsACString & aName)287 nsToolkitProfile::SetName(const nsACString& aName) {
288 NS_ASSERTION(nsToolkitProfileService::gService, "Where did my service go?");
289
290 if (mName.Equals(aName)) {
291 return NS_OK;
292 }
293
294 // Changing the name from the dev-edition default profile name makes this
295 // profile no longer the dev-edition default.
296 if (mName.EqualsLiteral(DEV_EDITION_NAME) &&
297 nsToolkitProfileService::gService->mDevEditionDefault == this) {
298 nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
299 }
300
301 mName = aName;
302
303 nsresult rv = nsToolkitProfileService::gService->mProfileDB.SetString(
304 mSection.get(), "Name", mName.get());
305 NS_ENSURE_SUCCESS(rv, rv);
306
307 // Setting the name to the dev-edition default profile name will cause this
308 // profile to become the dev-edition default.
309 if (aName.EqualsLiteral(DEV_EDITION_NAME) &&
310 !nsToolkitProfileService::gService->mDevEditionDefault) {
311 nsToolkitProfileService::gService->mDevEditionDefault = this;
312 }
313
314 return NS_OK;
315 }
316
RemoveInternal(bool aRemoveFiles,bool aInBackground)317 nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
318 bool aInBackground) {
319 NS_ASSERTION(nsToolkitProfileService::gService, "Whoa, my service is gone.");
320
321 if (mLock) return NS_ERROR_FILE_IS_LOCKED;
322
323 if (!isInList()) {
324 return NS_ERROR_NOT_INITIALIZED;
325 }
326
327 if (aRemoveFiles) {
328 RemoveProfileFiles(this, aInBackground);
329 }
330
331 nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
332 db->DeleteSection(mSection.get());
333
334 // We make some assumptions that the profile's index in the database is based
335 // on its position in the linked list. Removing a profile means we have to fix
336 // the index of later profiles in the list. The easiest way to do that is just
337 // to move the last profile into the profile's position and just update its
338 // index.
339 RefPtr<nsToolkitProfile> last =
340 nsToolkitProfileService::gService->mProfiles.getLast();
341 if (last != this) {
342 // Update the section in the db.
343 last->mIndex = mIndex;
344 db->RenameSection(last->mSection.get(), mSection.get());
345 last->mSection = mSection;
346
347 if (last != getNext()) {
348 last->remove();
349 setNext(last);
350 }
351 }
352
353 remove();
354
355 if (nsToolkitProfileService::gService->mNormalDefault == this) {
356 nsToolkitProfileService::gService->mNormalDefault = nullptr;
357 }
358 if (nsToolkitProfileService::gService->mDevEditionDefault == this) {
359 nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
360 }
361 if (nsToolkitProfileService::gService->mDedicatedProfile == this) {
362 nsToolkitProfileService::gService->SetDefaultProfile(nullptr);
363 }
364
365 return NS_OK;
366 }
367
368 NS_IMETHODIMP
Remove(bool removeFiles)369 nsToolkitProfile::Remove(bool removeFiles) {
370 return RemoveInternal(removeFiles, false /* in background */);
371 }
372
373 NS_IMETHODIMP
RemoveInBackground(bool removeFiles)374 nsToolkitProfile::RemoveInBackground(bool removeFiles) {
375 return RemoveInternal(removeFiles, true /* in background */);
376 }
377
378 NS_IMETHODIMP
Lock(nsIProfileUnlocker ** aUnlocker,nsIProfileLock ** aResult)379 nsToolkitProfile::Lock(nsIProfileUnlocker** aUnlocker,
380 nsIProfileLock** aResult) {
381 if (mLock) {
382 NS_ADDREF(*aResult = mLock);
383 return NS_OK;
384 }
385
386 RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
387
388 nsresult rv = lock->Init(this, aUnlocker);
389 if (NS_FAILED(rv)) return rv;
390
391 NS_ADDREF(*aResult = lock);
392 return NS_OK;
393 }
394
NS_IMPL_ISUPPORTS(nsToolkitProfileLock,nsIProfileLock)395 NS_IMPL_ISUPPORTS(nsToolkitProfileLock, nsIProfileLock)
396
397 nsresult nsToolkitProfileLock::Init(nsToolkitProfile* aProfile,
398 nsIProfileUnlocker** aUnlocker) {
399 nsresult rv;
400 rv = Init(aProfile->mRootDir, aProfile->mLocalDir, aUnlocker);
401 if (NS_SUCCEEDED(rv)) mProfile = aProfile;
402
403 return rv;
404 }
405
Init(nsIFile * aDirectory,nsIFile * aLocalDirectory,nsIProfileUnlocker ** aUnlocker)406 nsresult nsToolkitProfileLock::Init(nsIFile* aDirectory,
407 nsIFile* aLocalDirectory,
408 nsIProfileUnlocker** aUnlocker) {
409 nsresult rv;
410
411 rv = mLock.Lock(aDirectory, aUnlocker);
412
413 if (NS_SUCCEEDED(rv)) {
414 mDirectory = aDirectory;
415 mLocalDirectory = aLocalDirectory;
416 }
417
418 return rv;
419 }
420
421 NS_IMETHODIMP
GetDirectory(nsIFile ** aResult)422 nsToolkitProfileLock::GetDirectory(nsIFile** aResult) {
423 if (!mDirectory) {
424 NS_ERROR("Not initialized, or unlocked!");
425 return NS_ERROR_NOT_INITIALIZED;
426 }
427
428 NS_ADDREF(*aResult = mDirectory);
429 return NS_OK;
430 }
431
432 NS_IMETHODIMP
GetLocalDirectory(nsIFile ** aResult)433 nsToolkitProfileLock::GetLocalDirectory(nsIFile** aResult) {
434 if (!mLocalDirectory) {
435 NS_ERROR("Not initialized, or unlocked!");
436 return NS_ERROR_NOT_INITIALIZED;
437 }
438
439 NS_ADDREF(*aResult = mLocalDirectory);
440 return NS_OK;
441 }
442
443 NS_IMETHODIMP
Unlock()444 nsToolkitProfileLock::Unlock() {
445 if (!mDirectory) {
446 NS_ERROR("Unlocking a never-locked nsToolkitProfileLock!");
447 return NS_ERROR_UNEXPECTED;
448 }
449
450 // XXX If we get here with an active quota manager,
451 // something went very wrong. We want to assert this.
452
453 mLock.Unlock();
454
455 if (mProfile) {
456 mProfile->mLock = nullptr;
457 mProfile = nullptr;
458 }
459 mDirectory = nullptr;
460 mLocalDirectory = nullptr;
461
462 return NS_OK;
463 }
464
465 NS_IMETHODIMP
GetReplacedLockTime(PRTime * aResult)466 nsToolkitProfileLock::GetReplacedLockTime(PRTime* aResult) {
467 mLock.GetReplacedLockTime(aResult);
468 return NS_OK;
469 }
470
~nsToolkitProfileLock()471 nsToolkitProfileLock::~nsToolkitProfileLock() {
472 if (mDirectory) {
473 Unlock();
474 }
475 }
476
477 nsToolkitProfileService* nsToolkitProfileService::gService = nullptr;
478
NS_IMPL_ISUPPORTS(nsToolkitProfileService,nsIToolkitProfileService)479 NS_IMPL_ISUPPORTS(nsToolkitProfileService, nsIToolkitProfileService)
480
481 nsToolkitProfileService::nsToolkitProfileService()
482 : mStartupProfileSelected(false),
483 mStartWithLast(true),
484 mIsFirstRun(true),
485 mUseDevEditionProfile(false),
486 #ifdef MOZ_DEDICATED_PROFILES
487 mUseDedicatedProfile(!IsSnapEnvironment() && !UseLegacyProfiles()),
488 #else
489 mUseDedicatedProfile(false),
490 #endif
491 mStartupReason(u"unknown"_ns),
492 mMaybeLockProfile(false),
493 mUpdateChannel(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)),
494 mProfileDBExists(false),
495 mProfileDBFileSize(0),
496 mProfileDBModifiedTime(0) {
497 #ifdef MOZ_DEV_EDITION
498 mUseDevEditionProfile = true;
499 #endif
500 gService = this;
501 }
502
~nsToolkitProfileService()503 nsToolkitProfileService::~nsToolkitProfileService() {
504 gService = nullptr;
505 mProfiles.clear();
506 }
507
CompleteStartup()508 void nsToolkitProfileService::CompleteStartup() {
509 if (!mStartupProfileSelected) {
510 return;
511 }
512
513 ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_SELECTION_REASON,
514 mStartupReason);
515
516 if (mMaybeLockProfile) {
517 nsCOMPtr<nsIToolkitShellService> shell =
518 do_GetService(NS_TOOLKITSHELLSERVICE_CONTRACTID);
519 if (!shell) {
520 return;
521 }
522
523 bool isDefaultApp;
524 nsresult rv = shell->IsDefaultApplication(&isDefaultApp);
525 NS_ENSURE_SUCCESS_VOID(rv);
526
527 if (isDefaultApp) {
528 mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
529
530 // There is a very small chance that this could fail if something else
531 // overwrote the profiles database since we started up, probably less than
532 // a second ago. There isn't really a sane response here, all the other
533 // profile changes are already flushed so whether we fail to flush here or
534 // force quit the app makes no difference.
535 NS_ENSURE_SUCCESS_VOID(Flush());
536 }
537 }
538 }
539
540 // Tests whether the passed profile was last used by this install.
IsProfileForCurrentInstall(nsIToolkitProfile * aProfile)541 bool nsToolkitProfileService::IsProfileForCurrentInstall(
542 nsIToolkitProfile* aProfile) {
543 nsCOMPtr<nsIFile> profileDir;
544 nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
545 NS_ENSURE_SUCCESS(rv, false);
546
547 nsCOMPtr<nsIFile> compatFile;
548 rv = profileDir->Clone(getter_AddRefs(compatFile));
549 NS_ENSURE_SUCCESS(rv, false);
550
551 rv = compatFile->Append(COMPAT_FILE);
552 NS_ENSURE_SUCCESS(rv, false);
553
554 nsINIParser compatData;
555 rv = compatData.Init(compatFile);
556 NS_ENSURE_SUCCESS(rv, false);
557
558 /**
559 * In xpcshell gDirServiceProvider doesn't have all the correct directories
560 * set so using NS_GetSpecialDirectory works better there. But in a normal
561 * app launch the component registry isn't initialized so
562 * NS_GetSpecialDirectory doesn't work. So we have to use two different
563 * paths to support testing.
564 */
565 nsCOMPtr<nsIFile> currentGreDir;
566 rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(currentGreDir));
567 if (rv == NS_ERROR_NOT_INITIALIZED) {
568 currentGreDir = gDirServiceProvider->GetGREDir();
569 MOZ_ASSERT(currentGreDir, "No GRE dir found.");
570 } else if (NS_FAILED(rv)) {
571 return false;
572 }
573
574 nsCString lastGreDirStr;
575 rv = compatData.GetString("Compatibility", "LastPlatformDir", lastGreDirStr);
576 // If this string is missing then this profile is from an ancient version.
577 // We'll opt to use it in this case.
578 if (NS_FAILED(rv)) {
579 return true;
580 }
581
582 nsCOMPtr<nsIFile> lastGreDir;
583 rv = NS_NewNativeLocalFile(""_ns, false, getter_AddRefs(lastGreDir));
584 NS_ENSURE_SUCCESS(rv, false);
585
586 rv = lastGreDir->SetPersistentDescriptor(lastGreDirStr);
587 NS_ENSURE_SUCCESS(rv, false);
588
589 #ifdef XP_WIN
590 # if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
591 mozilla::PathString lastGreDirPath, currentGreDirPath;
592 lastGreDirPath = lastGreDir->NativePath();
593 currentGreDirPath = currentGreDir->NativePath();
594 if (lastGreDirPath.Equals(currentGreDirPath,
595 nsCaseInsensitiveStringComparator)) {
596 return true;
597 }
598
599 // Convert a 64-bit install path to what would have been the 32-bit install
600 // path to allow users to migrate their profiles from one to the other.
601 PWSTR pathX86 = nullptr;
602 HRESULT hres =
603 SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr, &pathX86);
604 if (SUCCEEDED(hres)) {
605 nsDependentString strPathX86(pathX86);
606 if (!StringBeginsWith(currentGreDirPath, strPathX86,
607 nsCaseInsensitiveStringComparator)) {
608 PWSTR path = nullptr;
609 hres = SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, nullptr, &path);
610 if (SUCCEEDED(hres)) {
611 if (StringBeginsWith(currentGreDirPath, nsDependentString(path),
612 nsCaseInsensitiveStringComparator)) {
613 currentGreDirPath.Replace(0, wcslen(path), strPathX86);
614 }
615 }
616 CoTaskMemFree(path);
617 }
618 }
619 CoTaskMemFree(pathX86);
620
621 return lastGreDirPath.Equals(currentGreDirPath,
622 nsCaseInsensitiveStringComparator);
623 # endif
624 #endif
625
626 bool equal;
627 rv = lastGreDir->Equals(currentGreDir, &equal);
628 NS_ENSURE_SUCCESS(rv, false);
629
630 return equal;
631 }
632
633 /**
634 * Used the first time an install with dedicated profile support runs. Decides
635 * whether to mark the passed profile as the default for this install.
636 *
637 * The goal is to reduce disruption but ideally end up with the OS default
638 * install using the old default profile.
639 *
640 * If the decision is to use the profile then it will be unassigned as the
641 * dedicated default for other installs.
642 *
643 * We won't attempt to use the profile if it was last used by a different
644 * install.
645 *
646 * If the profile is currently in use by an install that was either the OS
647 * default install or the profile has been explicitely chosen by some other
648 * means then we won't use it.
649 *
650 * aResult will be set to true if we chose to make the profile the new dedicated
651 * default.
652 */
MaybeMakeDefaultDedicatedProfile(nsIToolkitProfile * aProfile,bool * aResult)653 nsresult nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
654 nsIToolkitProfile* aProfile, bool* aResult) {
655 nsresult rv;
656 *aResult = false;
657
658 // If the profile was last used by a different install then we won't use it.
659 if (!IsProfileForCurrentInstall(aProfile)) {
660 return NS_OK;
661 }
662
663 nsCString descriptor;
664 rv = GetProfileDescriptor(aProfile, descriptor, nullptr);
665 NS_ENSURE_SUCCESS(rv, rv);
666
667 // Get a list of all the installs.
668 nsTArray<nsCString> installs = GetKnownInstalls();
669
670 // Cache the installs that use the profile.
671 nsTArray<nsCString> inUseInstalls;
672
673 // See if the profile is already in use by an install that hasn't locked it.
674 for (uint32_t i = 0; i < installs.Length(); i++) {
675 const nsCString& install = installs[i];
676
677 nsCString path;
678 rv = mProfileDB.GetString(install.get(), "Default", path);
679 if (NS_FAILED(rv)) {
680 continue;
681 }
682
683 // Is this install using the profile we care about?
684 if (!descriptor.Equals(path)) {
685 continue;
686 }
687
688 // Is this profile locked to this other install?
689 nsCString isLocked;
690 rv = mProfileDB.GetString(install.get(), "Locked", isLocked);
691 if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) {
692 return NS_OK;
693 }
694
695 inUseInstalls.AppendElement(install);
696 }
697
698 // At this point we've decided to take the profile. Strip it from other
699 // installs.
700 for (uint32_t i = 0; i < inUseInstalls.Length(); i++) {
701 // Removing the default setting entirely will make the install go through
702 // the first run process again at startup and create itself a new profile.
703 mProfileDB.DeleteString(inUseInstalls[i].get(), "Default");
704 }
705
706 // Set this as the default profile for this install.
707 SetDefaultProfile(aProfile);
708
709 // SetDefaultProfile will have locked this profile to this install so no
710 // other installs will steal it, but this was auto-selected so we want to
711 // unlock it so that other installs can potentially take it.
712 mProfileDB.DeleteString(mInstallSection.get(), "Locked");
713
714 // Persist the changes.
715 rv = Flush();
716 NS_ENSURE_SUCCESS(rv, rv);
717
718 // Once XPCOM is available check if this is the default application and if so
719 // lock the profile again.
720 mMaybeLockProfile = true;
721 *aResult = true;
722
723 return NS_OK;
724 }
725
IsFileOutdated(nsIFile * aFile,bool aExists,PRTime aLastModified,int64_t aLastSize)726 bool IsFileOutdated(nsIFile* aFile, bool aExists, PRTime aLastModified,
727 int64_t aLastSize) {
728 nsCOMPtr<nsIFile> file;
729 nsresult rv = aFile->Clone(getter_AddRefs(file));
730 if (NS_FAILED(rv)) {
731 return false;
732 }
733
734 bool exists;
735 rv = aFile->Exists(&exists);
736 if (NS_FAILED(rv) || exists != aExists) {
737 return true;
738 }
739
740 if (!exists) {
741 return false;
742 }
743
744 int64_t size;
745 rv = aFile->GetFileSize(&size);
746 if (NS_FAILED(rv) || size != aLastSize) {
747 return true;
748 }
749
750 PRTime time;
751 rv = aFile->GetLastModifiedTime(&time);
752 if (NS_FAILED(rv) || time != aLastModified) {
753 return true;
754 }
755
756 return false;
757 }
758
UpdateFileStats(nsIFile * aFile,bool * aExists,PRTime * aLastModified,int64_t * aLastSize)759 nsresult UpdateFileStats(nsIFile* aFile, bool* aExists, PRTime* aLastModified,
760 int64_t* aLastSize) {
761 nsCOMPtr<nsIFile> file;
762 nsresult rv = aFile->Clone(getter_AddRefs(file));
763 NS_ENSURE_SUCCESS(rv, rv);
764
765 rv = file->Exists(aExists);
766 NS_ENSURE_SUCCESS(rv, rv);
767
768 if (!(*aExists)) {
769 *aLastModified = 0;
770 *aLastSize = 0;
771 return NS_OK;
772 }
773
774 rv = file->GetFileSize(aLastSize);
775 NS_ENSURE_SUCCESS(rv, rv);
776
777 rv = file->GetLastModifiedTime(aLastModified);
778 NS_ENSURE_SUCCESS(rv, rv);
779
780 return NS_OK;
781 }
782
783 NS_IMETHODIMP
GetIsListOutdated(bool * aResult)784 nsToolkitProfileService::GetIsListOutdated(bool* aResult) {
785 if (IsFileOutdated(mProfileDBFile, mProfileDBExists, mProfileDBModifiedTime,
786 mProfileDBFileSize)) {
787 *aResult = true;
788 return NS_OK;
789 }
790
791 *aResult = false;
792 return NS_OK;
793 }
794
795 struct ImportInstallsClosure {
796 nsINIParser* backupData;
797 nsINIParser* profileDB;
798 };
799
ImportInstalls(const char * aSection,void * aClosure)800 static bool ImportInstalls(const char* aSection, void* aClosure) {
801 ImportInstallsClosure* closure =
802 static_cast<ImportInstallsClosure*>(aClosure);
803
804 nsTArray<UniquePtr<KeyValue>> strings =
805 GetSectionStrings(closure->backupData, aSection);
806 if (strings.IsEmpty()) {
807 return true;
808 }
809
810 nsCString newSection(INSTALL_PREFIX);
811 newSection.Append(aSection);
812 nsCString buffer;
813
814 for (uint32_t i = 0; i < strings.Length(); i++) {
815 closure->profileDB->SetString(newSection.get(), strings[i]->key.get(),
816 strings[i]->value.get());
817 }
818
819 return true;
820 }
821
Init()822 nsresult nsToolkitProfileService::Init() {
823 NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
824 nsresult rv;
825
826 rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(mAppData));
827 NS_ENSURE_SUCCESS(rv, rv);
828
829 rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData));
830 NS_ENSURE_SUCCESS(rv, rv);
831
832 rv = mAppData->Clone(getter_AddRefs(mProfileDBFile));
833 NS_ENSURE_SUCCESS(rv, rv);
834
835 rv = mProfileDBFile->AppendNative("profiles.ini"_ns);
836 NS_ENSURE_SUCCESS(rv, rv);
837
838 rv = mAppData->Clone(getter_AddRefs(mInstallDBFile));
839 NS_ENSURE_SUCCESS(rv, rv);
840
841 rv = mInstallDBFile->AppendNative("installs.ini"_ns);
842 NS_ENSURE_SUCCESS(rv, rv);
843
844 nsAutoCString buffer;
845
846 rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
847 &mProfileDBModifiedTime, &mProfileDBFileSize);
848 if (NS_SUCCEEDED(rv) && mProfileDBExists) {
849 rv = mProfileDB.Init(mProfileDBFile);
850 // Init does not fail on parsing errors, only on OOM/really unexpected
851 // conditions.
852 if (NS_FAILED(rv)) {
853 return rv;
854 }
855
856 rv = mProfileDB.GetString("General", "StartWithLastProfile", buffer);
857 if (NS_SUCCEEDED(rv)) {
858 mStartWithLast = !buffer.EqualsLiteral("0");
859 }
860
861 rv = mProfileDB.GetString("General", "Version", buffer);
862 if (NS_FAILED(rv)) {
863 // This is a profiles.ini written by an older version. We must restore
864 // any install data from the backup.
865 nsINIParser installDB;
866
867 if (NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
868 // There is install data to import.
869 ImportInstallsClosure closure = {&installDB, &mProfileDB};
870 installDB.GetSections(&ImportInstalls, &closure);
871 }
872
873 rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
874 NS_ENSURE_SUCCESS(rv, rv);
875 }
876 } else {
877 rv = mProfileDB.SetString("General", "StartWithLastProfile",
878 mStartWithLast ? "1" : "0");
879 NS_ENSURE_SUCCESS(rv, rv);
880 rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
881 NS_ENSURE_SUCCESS(rv, rv);
882 }
883
884 nsCString installProfilePath;
885
886 if (mUseDedicatedProfile) {
887 nsString installHash;
888 rv = gDirServiceProvider->GetInstallHash(installHash);
889 NS_ENSURE_SUCCESS(rv, rv);
890 CopyUTF16toUTF8(installHash, mInstallSection);
891 mInstallSection.Insert(INSTALL_PREFIX, 0);
892
893 // Try to find the descriptor for the default profile for this install.
894 rv = mProfileDB.GetString(mInstallSection.get(), "Default",
895 installProfilePath);
896
897 // Not having a value means this install doesn't appear in installs.ini so
898 // this is the first run for this install.
899 if (NS_FAILED(rv)) {
900 mIsFirstRun = true;
901
902 // Gets the install section that would have been created if the install
903 // path has incorrect casing (see bug 1555319). We use this later during
904 // profile selection.
905 rv = gDirServiceProvider->GetLegacyInstallHash(installHash);
906 NS_ENSURE_SUCCESS(rv, rv);
907 CopyUTF16toUTF8(installHash, mLegacyInstallSection);
908 mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
909 } else {
910 mIsFirstRun = false;
911 }
912 }
913
914 nsToolkitProfile* currentProfile = nullptr;
915
916 #ifdef MOZ_DEV_EDITION
917 nsCOMPtr<nsIFile> ignoreDevEditionProfile;
918 rv = mAppData->Clone(getter_AddRefs(ignoreDevEditionProfile));
919 if (NS_FAILED(rv)) {
920 return rv;
921 }
922
923 rv = ignoreDevEditionProfile->AppendNative("ignore-dev-edition-profile"_ns);
924 if (NS_FAILED(rv)) {
925 return rv;
926 }
927
928 bool shouldIgnoreSeparateProfile;
929 rv = ignoreDevEditionProfile->Exists(&shouldIgnoreSeparateProfile);
930 if (NS_FAILED(rv)) return rv;
931
932 mUseDevEditionProfile = !shouldIgnoreSeparateProfile;
933 #endif
934
935 nsCOMPtr<nsIToolkitProfile> autoSelectProfile;
936
937 unsigned int nonDevEditionProfiles = 0;
938 unsigned int c = 0;
939 for (c = 0; true; ++c) {
940 nsAutoCString profileID("Profile");
941 profileID.AppendInt(c);
942
943 rv = mProfileDB.GetString(profileID.get(), "IsRelative", buffer);
944 if (NS_FAILED(rv)) break;
945
946 bool isRelative = buffer.EqualsLiteral("1");
947
948 nsAutoCString filePath;
949
950 rv = mProfileDB.GetString(profileID.get(), "Path", filePath);
951 if (NS_FAILED(rv)) {
952 NS_ERROR("Malformed profiles.ini: Path= not found");
953 continue;
954 }
955
956 nsAutoCString name;
957
958 rv = mProfileDB.GetString(profileID.get(), "Name", name);
959 if (NS_FAILED(rv)) {
960 NS_ERROR("Malformed profiles.ini: Name= not found");
961 continue;
962 }
963
964 nsCOMPtr<nsIFile> rootDir;
965 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(rootDir));
966 NS_ENSURE_SUCCESS(rv, rv);
967
968 if (isRelative) {
969 rv = rootDir->SetRelativeDescriptor(mAppData, filePath);
970 } else {
971 rv = rootDir->SetPersistentDescriptor(filePath);
972 }
973 if (NS_FAILED(rv)) continue;
974
975 nsCOMPtr<nsIFile> localDir;
976 if (isRelative) {
977 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localDir));
978 NS_ENSURE_SUCCESS(rv, rv);
979
980 rv = localDir->SetRelativeDescriptor(mTempData, filePath);
981 } else {
982 localDir = rootDir;
983 }
984
985 currentProfile = new nsToolkitProfile(name, rootDir, localDir, true);
986
987 // If a user has modified the ini file path it may make for a valid profile
988 // path but not match what we would have serialised and so may not match
989 // the path in the install section. Re-serialise it to get it in the
990 // expected form again.
991 bool nowRelative;
992 nsCString descriptor;
993 GetProfileDescriptor(currentProfile, descriptor, &nowRelative);
994
995 if (isRelative != nowRelative || !descriptor.Equals(filePath)) {
996 mProfileDB.SetString(profileID.get(), "IsRelative",
997 nowRelative ? "1" : "0");
998 mProfileDB.SetString(profileID.get(), "Path", descriptor.get());
999
1000 // Should we flush now? It costs some startup time and we will fix it on
1001 // the next startup anyway. If something else causes a flush then it will
1002 // be fixed in the ini file then.
1003 }
1004
1005 rv = mProfileDB.GetString(profileID.get(), "Default", buffer);
1006 if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
1007 mNormalDefault = currentProfile;
1008 }
1009
1010 // Is this the default profile for this install?
1011 if (mUseDedicatedProfile && !mDedicatedProfile &&
1012 installProfilePath.Equals(descriptor)) {
1013 // Found a profile for this install.
1014 mDedicatedProfile = currentProfile;
1015 }
1016
1017 if (name.EqualsLiteral(DEV_EDITION_NAME)) {
1018 mDevEditionDefault = currentProfile;
1019 } else {
1020 nonDevEditionProfiles++;
1021 autoSelectProfile = currentProfile;
1022 }
1023 }
1024
1025 // If there is only one non-dev-edition profile then mark it as the default.
1026 if (!mNormalDefault && nonDevEditionProfiles == 1) {
1027 SetNormalDefault(autoSelectProfile);
1028 }
1029
1030 if (!mUseDedicatedProfile) {
1031 if (mUseDevEditionProfile) {
1032 // When using the separate dev-edition profile not finding it means this
1033 // is a first run.
1034 mIsFirstRun = !mDevEditionDefault;
1035 } else {
1036 // If there are no normal profiles then this is a first run.
1037 mIsFirstRun = nonDevEditionProfiles == 0;
1038 }
1039 }
1040
1041 return NS_OK;
1042 }
1043
1044 NS_IMETHODIMP
SetStartWithLastProfile(bool aValue)1045 nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
1046 if (mStartWithLast != aValue) {
1047 // Note: the skeleton ui (see PreXULSkeletonUI.cpp) depends on this
1048 // having this name and being under General. If that ever changes,
1049 // the skeleton UI will just need to be updated. If it changes frequently,
1050 // it's probably best we just mirror the value to the registry here.
1051 nsresult rv = mProfileDB.SetString("General", "StartWithLastProfile",
1052 aValue ? "1" : "0");
1053 NS_ENSURE_SUCCESS(rv, rv);
1054 mStartWithLast = aValue;
1055 }
1056 return NS_OK;
1057 }
1058
1059 NS_IMETHODIMP
GetStartWithLastProfile(bool * aResult)1060 nsToolkitProfileService::GetStartWithLastProfile(bool* aResult) {
1061 *aResult = mStartWithLast;
1062 return NS_OK;
1063 }
1064
1065 NS_IMETHODIMP
GetProfiles(nsISimpleEnumerator ** aResult)1066 nsToolkitProfileService::GetProfiles(nsISimpleEnumerator** aResult) {
1067 *aResult = new ProfileEnumerator(mProfiles.getFirst());
1068
1069 NS_ADDREF(*aResult);
1070 return NS_OK;
1071 }
1072
1073 NS_IMETHODIMP
HasMoreElements(bool * aResult)1074 nsToolkitProfileService::ProfileEnumerator::HasMoreElements(bool* aResult) {
1075 *aResult = mCurrent ? true : false;
1076 return NS_OK;
1077 }
1078
1079 NS_IMETHODIMP
GetNext(nsISupports ** aResult)1080 nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports** aResult) {
1081 if (!mCurrent) return NS_ERROR_FAILURE;
1082
1083 NS_ADDREF(*aResult = mCurrent);
1084
1085 mCurrent = mCurrent->getNext();
1086 return NS_OK;
1087 }
1088
1089 NS_IMETHODIMP
GetCurrentProfile(nsIToolkitProfile ** aResult)1090 nsToolkitProfileService::GetCurrentProfile(nsIToolkitProfile** aResult) {
1091 NS_IF_ADDREF(*aResult = mCurrent);
1092 return NS_OK;
1093 }
1094
1095 NS_IMETHODIMP
GetDefaultProfile(nsIToolkitProfile ** aResult)1096 nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile** aResult) {
1097 if (mUseDedicatedProfile) {
1098 NS_IF_ADDREF(*aResult = mDedicatedProfile);
1099 return NS_OK;
1100 }
1101
1102 if (mUseDevEditionProfile) {
1103 NS_IF_ADDREF(*aResult = mDevEditionDefault);
1104 return NS_OK;
1105 }
1106
1107 NS_IF_ADDREF(*aResult = mNormalDefault);
1108 return NS_OK;
1109 }
1110
SetNormalDefault(nsIToolkitProfile * aProfile)1111 void nsToolkitProfileService::SetNormalDefault(nsIToolkitProfile* aProfile) {
1112 if (mNormalDefault == aProfile) {
1113 return;
1114 }
1115
1116 if (mNormalDefault) {
1117 nsToolkitProfile* profile =
1118 static_cast<nsToolkitProfile*>(mNormalDefault.get());
1119 mProfileDB.DeleteString(profile->mSection.get(), "Default");
1120 }
1121
1122 mNormalDefault = aProfile;
1123
1124 if (mNormalDefault) {
1125 nsToolkitProfile* profile =
1126 static_cast<nsToolkitProfile*>(mNormalDefault.get());
1127 mProfileDB.SetString(profile->mSection.get(), "Default", "1");
1128 }
1129 }
1130
1131 NS_IMETHODIMP
SetDefaultProfile(nsIToolkitProfile * aProfile)1132 nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
1133 if (mUseDedicatedProfile) {
1134 if (mDedicatedProfile != aProfile) {
1135 if (!aProfile) {
1136 // Setting this to the empty string means no profile will be found on
1137 // startup but we'll recognise that this install has been used
1138 // previously.
1139 mProfileDB.SetString(mInstallSection.get(), "Default", "");
1140 } else {
1141 nsCString profilePath;
1142 nsresult rv = GetProfileDescriptor(aProfile, profilePath, nullptr);
1143 NS_ENSURE_SUCCESS(rv, rv);
1144
1145 mProfileDB.SetString(mInstallSection.get(), "Default",
1146 profilePath.get());
1147 }
1148 mDedicatedProfile = aProfile;
1149
1150 // Some kind of choice has happened here, lock this profile to this
1151 // install.
1152 mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
1153 }
1154 return NS_OK;
1155 }
1156
1157 if (mUseDevEditionProfile && aProfile != mDevEditionDefault) {
1158 // The separate profile is hardcoded.
1159 return NS_ERROR_FAILURE;
1160 }
1161
1162 SetNormalDefault(aProfile);
1163
1164 return NS_OK;
1165 }
1166
1167 // Gets the profile root directory descriptor for storing in profiles.ini or
1168 // installs.ini.
GetProfileDescriptor(nsIToolkitProfile * aProfile,nsACString & aDescriptor,bool * aIsRelative)1169 nsresult nsToolkitProfileService::GetProfileDescriptor(
1170 nsIToolkitProfile* aProfile, nsACString& aDescriptor, bool* aIsRelative) {
1171 nsCOMPtr<nsIFile> profileDir;
1172 nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
1173 NS_ENSURE_SUCCESS(rv, rv);
1174
1175 // if the profile dir is relative to appdir...
1176 bool isRelative;
1177 rv = mAppData->Contains(profileDir, &isRelative);
1178
1179 nsCString profilePath;
1180 if (NS_SUCCEEDED(rv) && isRelative) {
1181 // we use a relative descriptor
1182 rv = profileDir->GetRelativeDescriptor(mAppData, profilePath);
1183 } else {
1184 // otherwise, a persistent descriptor
1185 rv = profileDir->GetPersistentDescriptor(profilePath);
1186 }
1187 NS_ENSURE_SUCCESS(rv, rv);
1188
1189 aDescriptor.Assign(profilePath);
1190 if (aIsRelative) {
1191 *aIsRelative = isRelative;
1192 }
1193
1194 return NS_OK;
1195 }
1196
CreateDefaultProfile(nsIToolkitProfile ** aResult)1197 nsresult nsToolkitProfileService::CreateDefaultProfile(
1198 nsIToolkitProfile** aResult) {
1199 // Create a new default profile
1200 nsAutoCString name;
1201 if (mUseDevEditionProfile) {
1202 name.AssignLiteral(DEV_EDITION_NAME);
1203 } else if (mUseDedicatedProfile) {
1204 name.AppendPrintf("default-%s", mUpdateChannel.get());
1205 } else {
1206 name.AssignLiteral(DEFAULT_NAME);
1207 }
1208
1209 nsresult rv = CreateUniqueProfile(nullptr, name, aResult);
1210 NS_ENSURE_SUCCESS(rv, rv);
1211
1212 if (mUseDedicatedProfile) {
1213 SetDefaultProfile(mCurrent);
1214 } else if (mUseDevEditionProfile) {
1215 mDevEditionDefault = mCurrent;
1216 } else {
1217 SetNormalDefault(mCurrent);
1218 }
1219
1220 return NS_OK;
1221 }
1222
1223 /**
1224 * An implementation of SelectStartupProfile callable from JavaScript via XPCOM.
1225 * See nsIToolkitProfileService.idl.
1226 */
1227 NS_IMETHODIMP
SelectStartupProfile(const nsTArray<nsCString> & aArgv,bool aIsResetting,const nsACString & aUpdateChannel,const nsACString & aLegacyInstallHash,nsIFile ** aRootDir,nsIFile ** aLocalDir,nsIToolkitProfile ** aProfile,bool * aDidCreate)1228 nsToolkitProfileService::SelectStartupProfile(
1229 const nsTArray<nsCString>& aArgv, bool aIsResetting,
1230 const nsACString& aUpdateChannel, const nsACString& aLegacyInstallHash,
1231 nsIFile** aRootDir, nsIFile** aLocalDir, nsIToolkitProfile** aProfile,
1232 bool* aDidCreate) {
1233 int argc = aArgv.Length();
1234 // Our command line handling expects argv to be null-terminated so construct
1235 // an appropriate array.
1236 auto argv = MakeUnique<char*[]>(argc + 1);
1237 // Also, our command line handling removes things from the array without
1238 // freeing them so keep track of what we've created separately.
1239 auto allocated = MakeUnique<UniqueFreePtr<char>[]>(argc);
1240
1241 for (int i = 0; i < argc; i++) {
1242 allocated[i].reset(ToNewCString(aArgv[i]));
1243 argv[i] = allocated[i].get();
1244 }
1245 argv[argc] = nullptr;
1246
1247 mUpdateChannel = aUpdateChannel;
1248 if (!aLegacyInstallHash.IsEmpty()) {
1249 mLegacyInstallSection.Assign(aLegacyInstallHash);
1250 mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
1251 }
1252
1253 bool wasDefault;
1254 nsresult rv =
1255 SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
1256 aProfile, aDidCreate, &wasDefault);
1257
1258 // Since we were called outside of the normal startup path complete any
1259 // startup tasks.
1260 if (NS_SUCCEEDED(rv)) {
1261 CompleteStartup();
1262 }
1263
1264 return rv;
1265 }
1266
1267 /**
1268 * Selects or creates a profile to use based on the profiles database, any
1269 * environment variables and any command line arguments. Will not create
1270 * a profile if aIsResetting is true. The profile is selected based on this
1271 * order of preference:
1272 * * Environment variables (set when restarting the application).
1273 * * --profile command line argument.
1274 * * --createprofile command line argument (this also causes the app to exit).
1275 * * -p command line argument.
1276 * * A new profile created if this is the first run of the application.
1277 * * The default profile.
1278 * aRootDir and aLocalDir are set to the data and local directories for the
1279 * profile data. If a profile from the database was selected it will be
1280 * returned in aProfile.
1281 * aDidCreate will be set to true if a new profile was created.
1282 * This function should be called once at startup and will fail if called again.
1283 * aArgv should be an array of aArgc + 1 strings, the last element being null.
1284 * Both aArgv and aArgc will be mutated.
1285 */
SelectStartupProfile(int * aArgc,char * aArgv[],bool aIsResetting,nsIFile ** aRootDir,nsIFile ** aLocalDir,nsIToolkitProfile ** aProfile,bool * aDidCreate,bool * aWasDefaultSelection)1286 nsresult nsToolkitProfileService::SelectStartupProfile(
1287 int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
1288 nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate,
1289 bool* aWasDefaultSelection) {
1290 if (mStartupProfileSelected) {
1291 return NS_ERROR_ALREADY_INITIALIZED;
1292 }
1293
1294 mStartupProfileSelected = true;
1295 *aDidCreate = false;
1296 *aWasDefaultSelection = false;
1297
1298 nsresult rv;
1299 const char* arg;
1300
1301 // Use the profile specified in the environment variables (generally from an
1302 // app initiated restart).
1303 nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH");
1304 if (lf) {
1305 nsCOMPtr<nsIFile> localDir = GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
1306 if (!localDir) {
1307 localDir = lf;
1308 }
1309
1310 // Clear out flags that we handled (or should have handled!) last startup.
1311 const char* dummy;
1312 CheckArg(*aArgc, aArgv, "p", &dummy);
1313 CheckArg(*aArgc, aArgv, "profile", &dummy);
1314 CheckArg(*aArgc, aArgv, "profilemanager");
1315
1316 nsCOMPtr<nsIToolkitProfile> profile;
1317 GetProfileByDir(lf, localDir, getter_AddRefs(profile));
1318
1319 if (profile && mIsFirstRun && mUseDedicatedProfile) {
1320 if (profile ==
1321 (mUseDevEditionProfile ? mDevEditionDefault : mNormalDefault)) {
1322 // This is the first run of a dedicated profile build where the selected
1323 // profile is the previous default so we should either make it the
1324 // default profile for this install or push the user to a new profile.
1325
1326 bool result;
1327 rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
1328 NS_ENSURE_SUCCESS(rv, rv);
1329 if (result) {
1330 mStartupReason = u"restart-claimed-default"_ns;
1331
1332 mCurrent = profile;
1333 } else {
1334 rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
1335 if (NS_FAILED(rv)) {
1336 *aProfile = nullptr;
1337 return rv;
1338 }
1339
1340 rv = Flush();
1341 NS_ENSURE_SUCCESS(rv, rv);
1342
1343 mStartupReason = u"restart-skipped-default"_ns;
1344 *aDidCreate = true;
1345 }
1346
1347 NS_IF_ADDREF(*aProfile = mCurrent);
1348 mCurrent->GetRootDir(aRootDir);
1349 mCurrent->GetLocalDir(aLocalDir);
1350
1351 return NS_OK;
1352 }
1353 }
1354
1355 if (EnvHasValue("XRE_RESTARTED_BY_PROFILE_MANAGER")) {
1356 mStartupReason = u"profile-manager"_ns;
1357 } else if (aIsResetting) {
1358 mStartupReason = u"profile-reset"_ns;
1359 } else {
1360 mStartupReason = u"restart"_ns;
1361 }
1362
1363 mCurrent = profile;
1364 lf.forget(aRootDir);
1365 localDir.forget(aLocalDir);
1366 NS_IF_ADDREF(*aProfile = profile);
1367 return NS_OK;
1368 }
1369
1370 // Check the -profile command line argument. It accepts a single argument that
1371 // gives the path to use for the profile.
1372 ArgResult ar = CheckArg(*aArgc, aArgv, "profile", &arg);
1373 if (ar == ARG_BAD) {
1374 PR_fprintf(PR_STDERR, "Error: argument --profile requires a path\n");
1375 return NS_ERROR_FAILURE;
1376 }
1377 if (ar) {
1378 nsCOMPtr<nsIFile> lf;
1379 rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
1380 NS_ENSURE_SUCCESS(rv, rv);
1381
1382 // Make sure that the profile path exists and it's a directory.
1383 bool exists;
1384 rv = lf->Exists(&exists);
1385 NS_ENSURE_SUCCESS(rv, rv);
1386 if (!exists) {
1387 rv = lf->Create(nsIFile::DIRECTORY_TYPE, 0700);
1388 NS_ENSURE_SUCCESS(rv, rv);
1389 } else {
1390 bool isDir;
1391 rv = lf->IsDirectory(&isDir);
1392 NS_ENSURE_SUCCESS(rv, rv);
1393 if (!isDir) {
1394 PR_fprintf(
1395 PR_STDERR,
1396 "Error: argument --profile requires a path to a directory\n");
1397 return NS_ERROR_FAILURE;
1398 }
1399 }
1400
1401 mStartupReason = u"argument-profile"_ns;
1402
1403 GetProfileByDir(lf, nullptr, getter_AddRefs(mCurrent));
1404 NS_ADDREF(*aRootDir = lf);
1405 // If the root dir matched a profile then use its local dir, otherwise use
1406 // the root dir as the local dir.
1407 if (mCurrent) {
1408 mCurrent->GetLocalDir(aLocalDir);
1409 } else {
1410 lf.forget(aLocalDir);
1411 }
1412
1413 NS_IF_ADDREF(*aProfile = mCurrent);
1414 return NS_OK;
1415 }
1416
1417 // Check the -createprofile command line argument. It accepts a single
1418 // argument that is either the name for the new profile or the name followed
1419 // by the path to use.
1420 ar = CheckArg(*aArgc, aArgv, "createprofile", &arg, CheckArgFlag::RemoveArg);
1421 if (ar == ARG_BAD) {
1422 PR_fprintf(PR_STDERR,
1423 "Error: argument --createprofile requires a profile name\n");
1424 return NS_ERROR_FAILURE;
1425 }
1426 if (ar) {
1427 const char* delim = strchr(arg, ' ');
1428 nsCOMPtr<nsIToolkitProfile> profile;
1429 if (delim) {
1430 nsCOMPtr<nsIFile> lf;
1431 rv = NS_NewNativeLocalFile(nsDependentCString(delim + 1), true,
1432 getter_AddRefs(lf));
1433 if (NS_FAILED(rv)) {
1434 PR_fprintf(PR_STDERR, "Error: profile path not valid.\n");
1435 return rv;
1436 }
1437
1438 // As with --profile, assume that the given path will be used for the
1439 // main profile directory.
1440 rv = CreateProfile(lf, nsDependentCSubstring(arg, delim),
1441 getter_AddRefs(profile));
1442 } else {
1443 rv = CreateProfile(nullptr, nsDependentCString(arg),
1444 getter_AddRefs(profile));
1445 }
1446 // Some pathological arguments can make it this far
1447 if (NS_FAILED(rv) || NS_FAILED(Flush())) {
1448 PR_fprintf(PR_STDERR, "Error creating profile.\n");
1449 }
1450 return NS_ERROR_ABORT;
1451 }
1452
1453 // Check the -p command line argument. It either accepts a profile name and
1454 // uses that named profile or without a name it opens the profile manager.
1455 ar = CheckArg(*aArgc, aArgv, "p", &arg);
1456 if (ar == ARG_BAD) {
1457 return NS_ERROR_SHOW_PROFILE_MANAGER;
1458 }
1459 if (ar) {
1460 rv = GetProfileByName(nsDependentCString(arg), getter_AddRefs(mCurrent));
1461 if (NS_SUCCEEDED(rv)) {
1462 mStartupReason = u"argument-p"_ns;
1463
1464 mCurrent->GetRootDir(aRootDir);
1465 mCurrent->GetLocalDir(aLocalDir);
1466
1467 NS_ADDREF(*aProfile = mCurrent);
1468 return NS_OK;
1469 }
1470
1471 return NS_ERROR_SHOW_PROFILE_MANAGER;
1472 }
1473
1474 ar = CheckArg(*aArgc, aArgv, "profilemanager");
1475 if (ar == ARG_FOUND) {
1476 return NS_ERROR_SHOW_PROFILE_MANAGER;
1477 }
1478
1479 if (mIsFirstRun && mUseDedicatedProfile &&
1480 !mInstallSection.Equals(mLegacyInstallSection)) {
1481 // The default profile could be assigned to a hash generated from an
1482 // incorrectly cased version of the installation directory (see bug
1483 // 1555319). Ideally we'd do all this while loading profiles.ini but we
1484 // can't override the legacy section value before that for tests.
1485 nsCString defaultDescriptor;
1486 rv = mProfileDB.GetString(mLegacyInstallSection.get(), "Default",
1487 defaultDescriptor);
1488
1489 if (NS_SUCCEEDED(rv)) {
1490 // There is a default here, need to see if it matches any profiles.
1491 bool isRelative;
1492 nsCString descriptor;
1493
1494 for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1495 GetProfileDescriptor(profile, descriptor, &isRelative);
1496
1497 if (descriptor.Equals(defaultDescriptor)) {
1498 // Found the default profile. Copy the install section over to
1499 // the correct location. We leave the old info in place for older
1500 // versions of Firefox to use.
1501 nsTArray<UniquePtr<KeyValue>> strings =
1502 GetSectionStrings(&mProfileDB, mLegacyInstallSection.get());
1503 for (const auto& kv : strings) {
1504 mProfileDB.SetString(mInstallSection.get(), kv->key.get(),
1505 kv->value.get());
1506 }
1507
1508 // Flush now. This causes a small blip in startup but it should be
1509 // one time only whereas not flushing means we have to do this search
1510 // on every startup.
1511 Flush();
1512
1513 // Now start up with the found profile.
1514 mDedicatedProfile = profile;
1515 mIsFirstRun = false;
1516 break;
1517 }
1518 }
1519 }
1520 }
1521
1522 // If this is a first run then create a new profile.
1523 if (mIsFirstRun) {
1524 // If we're configured to always show the profile manager then don't create
1525 // a new profile to use.
1526 if (!mStartWithLast) {
1527 return NS_ERROR_SHOW_PROFILE_MANAGER;
1528 }
1529
1530 bool skippedDefaultProfile = false;
1531
1532 if (mUseDedicatedProfile) {
1533 // This is the first run of a dedicated profile install. We have to decide
1534 // whether to use the default profile used by non-dedicated-profile
1535 // installs or to create a new profile.
1536
1537 // Find what would have been the default profile for old installs.
1538 nsCOMPtr<nsIToolkitProfile> profile = mNormalDefault;
1539 if (mUseDevEditionProfile) {
1540 profile = mDevEditionDefault;
1541 }
1542
1543 if (profile) {
1544 nsCOMPtr<nsIFile> rootDir;
1545 profile->GetRootDir(getter_AddRefs(rootDir));
1546
1547 nsCOMPtr<nsIFile> compat;
1548 rootDir->Clone(getter_AddRefs(compat));
1549 compat->Append(COMPAT_FILE);
1550
1551 bool exists;
1552 rv = compat->Exists(&exists);
1553 NS_ENSURE_SUCCESS(rv, rv);
1554
1555 // If the file is missing then either this is an empty profile (likely
1556 // generated by bug 1518591) or it is from an ancient version. We'll opt
1557 // to leave it for older versions in this case.
1558 if (exists) {
1559 bool result;
1560 rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
1561 NS_ENSURE_SUCCESS(rv, rv);
1562 if (result) {
1563 mStartupReason = u"firstrun-claimed-default"_ns;
1564
1565 mCurrent = profile;
1566 rootDir.forget(aRootDir);
1567 profile->GetLocalDir(aLocalDir);
1568 profile.forget(aProfile);
1569 return NS_OK;
1570 }
1571
1572 // We're going to create a new profile for this install even though
1573 // another default exists.
1574 skippedDefaultProfile = true;
1575 }
1576 }
1577 }
1578
1579 rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
1580 if (NS_SUCCEEDED(rv)) {
1581 // If there is only one profile and it isn't meant to be the profile that
1582 // older versions of Firefox use then we must create a default profile
1583 // for older versions of Firefox to avoid the existing profile being
1584 // auto-selected.
1585 if ((mUseDedicatedProfile || mUseDevEditionProfile) &&
1586 mProfiles.getFirst() == mProfiles.getLast()) {
1587 nsCOMPtr<nsIToolkitProfile> newProfile;
1588 CreateProfile(nullptr, nsLiteralCString(DEFAULT_NAME),
1589 getter_AddRefs(newProfile));
1590 SetNormalDefault(newProfile);
1591 }
1592
1593 rv = Flush();
1594 NS_ENSURE_SUCCESS(rv, rv);
1595
1596 if (skippedDefaultProfile) {
1597 mStartupReason = u"firstrun-skipped-default"_ns;
1598 } else {
1599 mStartupReason = u"firstrun-created-default"_ns;
1600 }
1601
1602 // Use the new profile.
1603 mCurrent->GetRootDir(aRootDir);
1604 mCurrent->GetLocalDir(aLocalDir);
1605 NS_ADDREF(*aProfile = mCurrent);
1606
1607 *aDidCreate = true;
1608 return NS_OK;
1609 }
1610 }
1611
1612 GetDefaultProfile(getter_AddRefs(mCurrent));
1613
1614 // None of the profiles was marked as default (generally only happens if the
1615 // user modifies profiles.ini manually). Let the user choose.
1616 if (!mCurrent) {
1617 return NS_ERROR_SHOW_PROFILE_MANAGER;
1618 }
1619
1620 // Let the caller know that the profile was selected by default.
1621 *aWasDefaultSelection = true;
1622 mStartupReason = u"default"_ns;
1623
1624 // Use the selected profile.
1625 mCurrent->GetRootDir(aRootDir);
1626 mCurrent->GetLocalDir(aLocalDir);
1627 NS_ADDREF(*aProfile = mCurrent);
1628
1629 return NS_OK;
1630 }
1631
1632 /**
1633 * Creates a new profile for reset and mark it as the current profile.
1634 */
CreateResetProfile(nsIToolkitProfile ** aNewProfile)1635 nsresult nsToolkitProfileService::CreateResetProfile(
1636 nsIToolkitProfile** aNewProfile) {
1637 nsAutoCString oldProfileName;
1638 mCurrent->GetName(oldProfileName);
1639
1640 nsCOMPtr<nsIToolkitProfile> newProfile;
1641 // Make the new profile name the old profile (or "default-") + the time in
1642 // seconds since epoch for uniqueness.
1643 nsAutoCString newProfileName;
1644 if (!oldProfileName.IsEmpty()) {
1645 newProfileName.Assign(oldProfileName);
1646 newProfileName.Append("-");
1647 } else {
1648 newProfileName.AssignLiteral("default-");
1649 }
1650 newProfileName.AppendPrintf("%" PRId64, PR_Now() / 1000);
1651 nsresult rv = CreateProfile(nullptr, // choose a default dir for us
1652 newProfileName, getter_AddRefs(newProfile));
1653 if (NS_FAILED(rv)) return rv;
1654
1655 mCurrent = newProfile;
1656 newProfile.forget(aNewProfile);
1657
1658 // Don't flush the changes yet. That will happen once the migration
1659 // successfully completes.
1660 return NS_OK;
1661 }
1662
1663 /**
1664 * This is responsible for deleting the old profile, copying its name to the
1665 * current profile and if the old profile was default making the new profile
1666 * default as well.
1667 */
ApplyResetProfile(nsIToolkitProfile * aOldProfile)1668 nsresult nsToolkitProfileService::ApplyResetProfile(
1669 nsIToolkitProfile* aOldProfile) {
1670 // If the old profile would have been the default for old installs then mark
1671 // the new profile as such.
1672 if (mNormalDefault == aOldProfile) {
1673 SetNormalDefault(mCurrent);
1674 }
1675
1676 if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) {
1677 bool wasLocked = false;
1678 nsCString val;
1679 if (NS_SUCCEEDED(
1680 mProfileDB.GetString(mInstallSection.get(), "Locked", val))) {
1681 wasLocked = val.Equals("1");
1682 }
1683
1684 SetDefaultProfile(mCurrent);
1685
1686 // Make the locked state match if necessary.
1687 if (!wasLocked) {
1688 mProfileDB.DeleteString(mInstallSection.get(), "Locked");
1689 }
1690 }
1691
1692 nsCString name;
1693 nsresult rv = aOldProfile->GetName(name);
1694 NS_ENSURE_SUCCESS(rv, rv);
1695
1696 // Don't remove the old profile's files until after we've successfully flushed
1697 // the profile changes to disk.
1698 rv = aOldProfile->Remove(false);
1699 NS_ENSURE_SUCCESS(rv, rv);
1700
1701 // Switching the name will make this the default for dev-edition if
1702 // appropriate.
1703 rv = mCurrent->SetName(name);
1704 NS_ENSURE_SUCCESS(rv, rv);
1705
1706 rv = Flush();
1707 NS_ENSURE_SUCCESS(rv, rv);
1708
1709 // Now that the profile changes are flushed, try to remove the old profile's
1710 // files. If we fail the worst that will happen is that an orphan directory is
1711 // left. Let this run in the background while we start up.
1712 RemoveProfileFiles(aOldProfile, true);
1713
1714 return NS_OK;
1715 }
1716
1717 NS_IMETHODIMP
GetProfileByName(const nsACString & aName,nsIToolkitProfile ** aResult)1718 nsToolkitProfileService::GetProfileByName(const nsACString& aName,
1719 nsIToolkitProfile** aResult) {
1720 for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1721 if (profile->mName.Equals(aName)) {
1722 NS_ADDREF(*aResult = profile);
1723 return NS_OK;
1724 }
1725 }
1726
1727 return NS_ERROR_FAILURE;
1728 }
1729
1730 /**
1731 * Finds a profile from the database that uses the given root and local
1732 * directories.
1733 */
GetProfileByDir(nsIFile * aRootDir,nsIFile * aLocalDir,nsIToolkitProfile ** aResult)1734 void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
1735 nsIFile* aLocalDir,
1736 nsIToolkitProfile** aResult) {
1737 for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1738 bool equal;
1739 nsresult rv = profile->mRootDir->Equals(aRootDir, &equal);
1740 if (NS_SUCCEEDED(rv) && equal) {
1741 if (!aLocalDir) {
1742 // If no local directory was given then we will just use the normal
1743 // local directory for the profile.
1744 profile.forget(aResult);
1745 return;
1746 }
1747
1748 rv = profile->mLocalDir->Equals(aLocalDir, &equal);
1749 if (NS_SUCCEEDED(rv) && equal) {
1750 profile.forget(aResult);
1751 return;
1752 }
1753 }
1754 }
1755 }
1756
NS_LockProfilePath(nsIFile * aPath,nsIFile * aTempPath,nsIProfileUnlocker ** aUnlocker,nsIProfileLock ** aResult)1757 nsresult NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
1758 nsIProfileUnlocker** aUnlocker,
1759 nsIProfileLock** aResult) {
1760 RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
1761
1762 nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
1763 if (NS_FAILED(rv)) return rv;
1764
1765 lock.forget(aResult);
1766 return NS_OK;
1767 }
1768
SaltProfileName(nsACString & aName)1769 static void SaltProfileName(nsACString& aName) {
1770 char salt[9];
1771 NS_MakeRandomString(salt, 8);
1772 salt[8] = '.';
1773
1774 aName.Insert(salt, 0, 9);
1775 }
1776
1777 NS_IMETHODIMP
CreateUniqueProfile(nsIFile * aRootDir,const nsACString & aNamePrefix,nsIToolkitProfile ** aResult)1778 nsToolkitProfileService::CreateUniqueProfile(nsIFile* aRootDir,
1779 const nsACString& aNamePrefix,
1780 nsIToolkitProfile** aResult) {
1781 nsCOMPtr<nsIToolkitProfile> profile;
1782 nsresult rv = GetProfileByName(aNamePrefix, getter_AddRefs(profile));
1783 if (NS_FAILED(rv)) {
1784 return CreateProfile(aRootDir, aNamePrefix, aResult);
1785 }
1786
1787 uint32_t suffix = 1;
1788 while (true) {
1789 nsPrintfCString name("%s-%d", PromiseFlatCString(aNamePrefix).get(),
1790 suffix);
1791 rv = GetProfileByName(name, getter_AddRefs(profile));
1792 if (NS_FAILED(rv)) {
1793 return CreateProfile(aRootDir, name, aResult);
1794 }
1795 suffix++;
1796 }
1797 }
1798
1799 NS_IMETHODIMP
CreateProfile(nsIFile * aRootDir,const nsACString & aName,nsIToolkitProfile ** aResult)1800 nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
1801 const nsACString& aName,
1802 nsIToolkitProfile** aResult) {
1803 nsresult rv = GetProfileByName(aName, aResult);
1804 if (NS_SUCCEEDED(rv)) {
1805 return rv;
1806 }
1807
1808 nsCOMPtr<nsIFile> rootDir(aRootDir);
1809
1810 nsAutoCString dirName;
1811 if (!rootDir) {
1812 rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir));
1813 NS_ENSURE_SUCCESS(rv, rv);
1814
1815 dirName = aName;
1816 SaltProfileName(dirName);
1817
1818 if (NS_IsNativeUTF8()) {
1819 rootDir->AppendNative(dirName);
1820 } else {
1821 rootDir->Append(NS_ConvertUTF8toUTF16(dirName));
1822 }
1823 }
1824
1825 nsCOMPtr<nsIFile> localDir;
1826
1827 bool isRelative;
1828 rv = mAppData->Contains(rootDir, &isRelative);
1829 if (NS_SUCCEEDED(rv) && isRelative) {
1830 nsAutoCString path;
1831 rv = rootDir->GetRelativeDescriptor(mAppData, path);
1832 NS_ENSURE_SUCCESS(rv, rv);
1833
1834 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localDir));
1835 NS_ENSURE_SUCCESS(rv, rv);
1836
1837 rv = localDir->SetRelativeDescriptor(mTempData, path);
1838 } else {
1839 localDir = rootDir;
1840 }
1841
1842 bool exists;
1843 rv = rootDir->Exists(&exists);
1844 NS_ENSURE_SUCCESS(rv, rv);
1845
1846 if (exists) {
1847 rv = rootDir->IsDirectory(&exists);
1848 NS_ENSURE_SUCCESS(rv, rv);
1849
1850 if (!exists) return NS_ERROR_FILE_NOT_DIRECTORY;
1851 } else {
1852 nsCOMPtr<nsIFile> profileDirParent;
1853 nsAutoString profileDirName;
1854
1855 rv = rootDir->GetParent(getter_AddRefs(profileDirParent));
1856 NS_ENSURE_SUCCESS(rv, rv);
1857
1858 rv = rootDir->GetLeafName(profileDirName);
1859 NS_ENSURE_SUCCESS(rv, rv);
1860
1861 // let's ensure that the profile directory exists.
1862 rv = rootDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1863 NS_ENSURE_SUCCESS(rv, rv);
1864 rv = rootDir->SetPermissions(0700);
1865 #ifndef ANDROID
1866 // If the profile is on the sdcard, this will fail but its non-fatal
1867 NS_ENSURE_SUCCESS(rv, rv);
1868 #endif
1869 }
1870
1871 rv = localDir->Exists(&exists);
1872 NS_ENSURE_SUCCESS(rv, rv);
1873
1874 if (!exists) {
1875 rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1876 NS_ENSURE_SUCCESS(rv, rv);
1877 }
1878
1879 // We created a new profile dir. Let's store a creation timestamp.
1880 // Note that this code path does not apply if the profile dir was
1881 // created prior to launching.
1882 rv = CreateTimesInternal(rootDir);
1883 NS_ENSURE_SUCCESS(rv, rv);
1884
1885 nsCOMPtr<nsIToolkitProfile> profile =
1886 new nsToolkitProfile(aName, rootDir, localDir, false);
1887
1888 if (aName.Equals(DEV_EDITION_NAME)) {
1889 mDevEditionDefault = profile;
1890 }
1891
1892 profile.forget(aResult);
1893 return NS_OK;
1894 }
1895
1896 /**
1897 * Snaps (https://snapcraft.io/) use a different installation directory for
1898 * every version of an application. Since dedicated profiles uses the
1899 * installation directory to determine which profile to use this would lead
1900 * snap users getting a new profile on every application update.
1901 *
1902 * However the only way to have multiple installation of a snap is to install
1903 * a new snap instance. Different snap instances have different user data
1904 * directories and so already will not share profiles, in fact one instance
1905 * will not even be able to see the other instance's profiles since
1906 * profiles.ini will be stored in different places.
1907 *
1908 * So we can just disable dedicated profile support in this case and revert
1909 * back to the old method of just having a single default profile and still
1910 * get essentially the same benefits as dedicated profiles provides.
1911 */
IsSnapEnvironment()1912 bool nsToolkitProfileService::IsSnapEnvironment() {
1913 #ifdef MOZ_WIDGET_GTK
1914 return widget::IsRunningUnderSnap();
1915 #else
1916 return false;
1917 #endif
1918 }
1919
1920 /**
1921 * In some situations dedicated profile support does not work well. This
1922 * includes a handful of linux distributions which always install different
1923 * application versions to different locations, some application sandboxing
1924 * systems as well as enterprise deployments. This environment variable provides
1925 * a way to opt out of dedicated profiles for these cases.
1926 *
1927 * For Windows, we provide a policy to accomplish the same thing.
1928 */
UseLegacyProfiles()1929 bool nsToolkitProfileService::UseLegacyProfiles() {
1930 bool legacyProfiles = !!PR_GetEnv("MOZ_LEGACY_PROFILES");
1931 #ifdef XP_WIN
1932 legacyProfiles |= PolicyCheckBoolean(L"LegacyProfiles");
1933 #endif
1934 return legacyProfiles;
1935 }
1936
1937 struct FindInstallsClosure {
1938 nsINIParser* installData;
1939 nsTArray<nsCString>* installs;
1940 };
1941
FindInstalls(const char * aSection,void * aClosure)1942 static bool FindInstalls(const char* aSection, void* aClosure) {
1943 FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure);
1944
1945 // Check if the section starts with "Install"
1946 if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
1947 return true;
1948 }
1949
1950 nsCString install(aSection);
1951 closure->installs->AppendElement(install);
1952
1953 return true;
1954 }
1955
GetKnownInstalls()1956 nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
1957 nsTArray<nsCString> result;
1958 FindInstallsClosure closure = {&mProfileDB, &result};
1959
1960 mProfileDB.GetSections(&FindInstalls, &closure);
1961
1962 return result;
1963 }
1964
CreateTimesInternal(nsIFile * aProfileDir)1965 nsresult nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir) {
1966 nsresult rv = NS_ERROR_FAILURE;
1967 nsCOMPtr<nsIFile> creationLog;
1968 rv = aProfileDir->Clone(getter_AddRefs(creationLog));
1969 NS_ENSURE_SUCCESS(rv, rv);
1970
1971 rv = creationLog->AppendNative("times.json"_ns);
1972 NS_ENSURE_SUCCESS(rv, rv);
1973
1974 bool exists = false;
1975 creationLog->Exists(&exists);
1976 if (exists) {
1977 return NS_OK;
1978 }
1979
1980 rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
1981 NS_ENSURE_SUCCESS(rv, rv);
1982
1983 // We don't care about microsecond resolution.
1984 int64_t msec = PR_Now() / PR_USEC_PER_MSEC;
1985
1986 // Write it out.
1987 PRFileDesc* writeFile;
1988 rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
1989 NS_ENSURE_SUCCESS(rv, rv);
1990
1991 PR_fprintf(writeFile, "{\n\"created\": %lld,\n\"firstUse\": null\n}\n", msec);
1992 PR_Close(writeFile);
1993 return NS_OK;
1994 }
1995
1996 NS_IMETHODIMP
GetProfileCount(uint32_t * aResult)1997 nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
1998 *aResult = 0;
1999 for (nsToolkitProfile* profile : mProfiles) {
2000 Unused << profile;
2001 (*aResult)++;
2002 }
2003
2004 return NS_OK;
2005 }
2006
2007 NS_IMETHODIMP
Flush()2008 nsToolkitProfileService::Flush() {
2009 if (GetIsListOutdated()) {
2010 return NS_ERROR_DATABASE_CHANGED;
2011 }
2012
2013 nsresult rv;
2014
2015 // If we aren't using dedicated profiles then nothing about the list of
2016 // installs can have changed, so no need to update the backup.
2017 if (mUseDedicatedProfile) {
2018 // Export the installs to the backup.
2019 nsTArray<nsCString> installs = GetKnownInstalls();
2020
2021 if (!installs.IsEmpty()) {
2022 nsCString data;
2023 nsCString buffer;
2024
2025 for (uint32_t i = 0; i < installs.Length(); i++) {
2026 nsTArray<UniquePtr<KeyValue>> strings =
2027 GetSectionStrings(&mProfileDB, installs[i].get());
2028 if (strings.IsEmpty()) {
2029 continue;
2030 }
2031
2032 // Strip "Install" from the start.
2033 const nsDependentCSubstring& install =
2034 Substring(installs[i], INSTALL_PREFIX_LENGTH);
2035 data.AppendPrintf("[%s]\n", PromiseFlatCString(install).get());
2036
2037 for (uint32_t j = 0; j < strings.Length(); j++) {
2038 data.AppendPrintf("%s=%s\n", strings[j]->key.get(),
2039 strings[j]->value.get());
2040 }
2041
2042 data.Append("\n");
2043 }
2044
2045 FILE* writeFile;
2046 rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
2047 NS_ENSURE_SUCCESS(rv, rv);
2048
2049 uint32_t length = data.Length();
2050 if (fwrite(data.get(), sizeof(char), length, writeFile) != length) {
2051 fclose(writeFile);
2052 return NS_ERROR_UNEXPECTED;
2053 }
2054
2055 fclose(writeFile);
2056 } else {
2057 rv = mInstallDBFile->Remove(false);
2058 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST &&
2059 rv != NS_ERROR_FILE_NOT_FOUND) {
2060 return rv;
2061 }
2062 }
2063 }
2064
2065 rv = mProfileDB.WriteToFile(mProfileDBFile);
2066 NS_ENSURE_SUCCESS(rv, rv);
2067
2068 rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
2069 &mProfileDBModifiedTime, &mProfileDBFileSize);
2070 NS_ENSURE_SUCCESS(rv, rv);
2071
2072 return NS_OK;
2073 }
2074
NS_IMPL_ISUPPORTS(nsToolkitProfileFactory,nsIFactory)2075 NS_IMPL_ISUPPORTS(nsToolkitProfileFactory, nsIFactory)
2076
2077 NS_IMETHODIMP
2078 nsToolkitProfileFactory::CreateInstance(nsISupports* aOuter, const nsID& aIID,
2079 void** aResult) {
2080 if (aOuter) return NS_ERROR_NO_AGGREGATION;
2081
2082 RefPtr<nsToolkitProfileService> profileService =
2083 nsToolkitProfileService::gService;
2084 if (!profileService) {
2085 nsresult rv = NS_NewToolkitProfileService(getter_AddRefs(profileService));
2086 if (NS_FAILED(rv)) return rv;
2087 }
2088 return profileService->QueryInterface(aIID, aResult);
2089 }
2090
2091 NS_IMETHODIMP
LockFactory(bool aVal)2092 nsToolkitProfileFactory::LockFactory(bool aVal) { return NS_OK; }
2093
NS_NewToolkitProfileFactory(nsIFactory ** aResult)2094 nsresult NS_NewToolkitProfileFactory(nsIFactory** aResult) {
2095 *aResult = new nsToolkitProfileFactory();
2096
2097 NS_ADDREF(*aResult);
2098 return NS_OK;
2099 }
2100
NS_NewToolkitProfileService(nsToolkitProfileService ** aResult)2101 nsresult NS_NewToolkitProfileService(nsToolkitProfileService** aResult) {
2102 nsToolkitProfileService* profileService = new nsToolkitProfileService();
2103 nsresult rv = profileService->Init();
2104 if (NS_FAILED(rv)) {
2105 NS_ERROR("nsToolkitProfileService::Init failed!");
2106 delete profileService;
2107 return rv;
2108 }
2109
2110 NS_ADDREF(*aResult = profileService);
2111 return NS_OK;
2112 }
2113
XRE_GetFileFromPath(const char * aPath,nsIFile ** aResult)2114 nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult) {
2115 #if defined(XP_MACOSX)
2116 int32_t pathLen = strlen(aPath);
2117 if (pathLen > MAXPATHLEN) return NS_ERROR_INVALID_ARG;
2118
2119 CFURLRef fullPath = CFURLCreateFromFileSystemRepresentation(
2120 nullptr, (const UInt8*)aPath, pathLen, true);
2121 if (!fullPath) return NS_ERROR_FAILURE;
2122
2123 nsCOMPtr<nsIFile> lf;
2124 nsresult rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(lf));
2125 if (NS_SUCCEEDED(rv)) {
2126 nsCOMPtr<nsILocalFileMac> lfMac = do_QueryInterface(lf, &rv);
2127 if (NS_SUCCEEDED(rv)) {
2128 rv = lfMac->InitWithCFURL(fullPath);
2129 if (NS_SUCCEEDED(rv)) {
2130 lf.forget(aResult);
2131 }
2132 }
2133 }
2134 CFRelease(fullPath);
2135 return rv;
2136
2137 #elif defined(XP_UNIX)
2138 char fullPath[MAXPATHLEN];
2139
2140 if (!realpath(aPath, fullPath)) return NS_ERROR_FAILURE;
2141
2142 return NS_NewNativeLocalFile(nsDependentCString(fullPath), true, aResult);
2143 #elif defined(XP_WIN)
2144 WCHAR fullPath[MAXPATHLEN];
2145
2146 if (!_wfullpath(fullPath, NS_ConvertUTF8toUTF16(aPath).get(), MAXPATHLEN))
2147 return NS_ERROR_FAILURE;
2148
2149 return NS_NewLocalFile(nsDependentString(fullPath), true, aResult);
2150
2151 #else
2152 # error Platform-specific logic needed here.
2153 #endif
2154 }
2155