Bug 1879109 [wpt PR 44441] - Add tests for animation of CSS calc-size()., a=testonly
[gecko.git] / toolkit / profile / nsToolkitProfileService.cpp
blobaeab25c61f3b04cfa19ec93e2abe0772d8656e61
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/. */
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"
13 #include <stdio.h>
14 #include <stdlib.h>
15 #include <prprf.h>
16 #include <prtime.h>
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
27 #include "nsToolkitProfileService.h"
28 #include "CmdLineAndEnvUtils.h"
29 #include "nsIFile.h"
31 #ifdef XP_MACOSX
32 # include <CoreFoundation/CoreFoundation.h>
33 # include "nsILocalFileMac.h"
34 #endif
36 #ifdef MOZ_WIDGET_GTK
37 # include "mozilla/WidgetUtilsGtk.h"
38 #endif
40 #include "nsAppDirectoryServiceDefs.h"
41 #include "nsDirectoryServiceDefs.h"
42 #include "nsNetCID.h"
43 #include "nsXULAppAPI.h"
44 #include "nsThreadUtils.h"
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 #ifdef MOZ_BACKGROUNDTASKS
62 # include "mozilla/BackgroundTasks.h"
63 # include "SpecialSystemDirectory.h"
64 #endif
66 using namespace mozilla;
68 #define DEV_EDITION_NAME "dev-edition-default"
69 #define DEFAULT_NAME "default"
70 #define COMPAT_FILE u"compatibility.ini"_ns
71 #define PROFILE_DB_VERSION "2"
72 #define INSTALL_PREFIX "Install"
73 #define INSTALL_PREFIX_LENGTH 7
75 struct KeyValue {
76 KeyValue(const char* aKey, const char* aValue) : key(aKey), value(aValue) {}
78 nsCString key;
79 nsCString value;
82 static bool GetStrings(const char* aString, const char* aValue,
83 void* aClosure) {
84 nsTArray<UniquePtr<KeyValue>>* array =
85 static_cast<nsTArray<UniquePtr<KeyValue>>*>(aClosure);
86 array->AppendElement(MakeUnique<KeyValue>(aString, aValue));
88 return true;
91 /**
92 * Returns an array of the strings inside a section of an ini file.
94 nsTArray<UniquePtr<KeyValue>> GetSectionStrings(nsINIParser* aParser,
95 const char* aSection) {
96 nsTArray<UniquePtr<KeyValue>> result;
97 aParser->GetStrings(aSection, &GetStrings, &result);
98 return result;
101 void RemoveProfileRecursion(const nsCOMPtr<nsIFile>& aDirectoryOrFile,
102 bool aIsIgnoreRoot, bool aIsIgnoreLockfile,
103 nsTArray<nsCOMPtr<nsIFile>>& aOutUndeletedFiles) {
104 auto guardDeletion = MakeScopeExit(
105 [&] { aOutUndeletedFiles.AppendElement(aDirectoryOrFile); });
107 // We actually would not expect to see links in our profiles, but still.
108 bool isLink = false;
109 NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsSymlink(&isLink));
111 // Only check to see if we have a directory if it isn't a link.
112 bool isDir = false;
113 if (!isLink) {
114 NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->IsDirectory(&isDir));
117 if (isDir) {
118 nsCOMPtr<nsIDirectoryEnumerator> dirEnum;
119 NS_ENSURE_SUCCESS_VOID(
120 aDirectoryOrFile->GetDirectoryEntries(getter_AddRefs(dirEnum)));
122 bool more = false;
123 while (NS_SUCCEEDED(dirEnum->HasMoreElements(&more)) && more) {
124 nsCOMPtr<nsISupports> item;
125 dirEnum->GetNext(getter_AddRefs(item));
126 nsCOMPtr<nsIFile> file = do_QueryInterface(item);
127 if (file) {
128 // Do not delete the profile lock.
129 if (aIsIgnoreLockfile && nsProfileLock::IsMaybeLockFile(file)) continue;
130 // If some children's remove fails, we still continue the loop.
131 RemoveProfileRecursion(file, false, false, aOutUndeletedFiles);
135 // Do not delete the root directory (yet).
136 if (!aIsIgnoreRoot) {
137 NS_ENSURE_SUCCESS_VOID(aDirectoryOrFile->Remove(false));
139 guardDeletion.release();
142 void RemoveProfileFiles(nsIToolkitProfile* aProfile, bool aInBackground) {
143 nsCOMPtr<nsIFile> rootDir;
144 aProfile->GetRootDir(getter_AddRefs(rootDir));
145 nsCOMPtr<nsIFile> localDir;
146 aProfile->GetLocalDir(getter_AddRefs(localDir));
148 // XXX If we get here with an active quota manager,
149 // something went very wrong. We want to assert this.
151 // Just lock the directories, don't mark the profile as locked or the lock
152 // will attempt to release its reference to the profile on the background
153 // thread which will assert.
154 nsCOMPtr<nsIProfileLock> lock;
155 NS_ENSURE_SUCCESS_VOID(
156 NS_LockProfilePath(rootDir, localDir, nullptr, getter_AddRefs(lock)));
158 nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
159 "nsToolkitProfile::RemoveProfileFiles",
160 [rootDir, localDir, lock]() mutable {
161 // We try to remove every single file and directory and collect
162 // those whose removal failed.
163 nsTArray<nsCOMPtr<nsIFile>> undeletedFiles;
164 // The root dir might contain the temp dir, so remove the temp dir
165 // first.
166 bool equals;
167 nsresult rv = rootDir->Equals(localDir, &equals);
168 if (NS_SUCCEEDED(rv) && !equals) {
169 RemoveProfileRecursion(localDir,
170 /* aIsIgnoreRoot */ false,
171 /* aIsIgnoreLockfile */ false, undeletedFiles);
173 // Now remove the content of the profile dir (except lockfile)
174 RemoveProfileRecursion(rootDir,
175 /* aIsIgnoreRoot */ true,
176 /* aIsIgnoreLockfile */ true, undeletedFiles);
178 // Retry loop if something was not deleted
179 if (undeletedFiles.Length() > 0) {
180 uint32_t retries = 1;
181 // XXX: Until bug 1716291 is fixed we just make one retry
182 while (undeletedFiles.Length() > 0 && retries <= 1) {
183 Unused << PR_Sleep(PR_MillisecondsToInterval(10 * retries));
184 for (auto&& file :
185 std::exchange(undeletedFiles, nsTArray<nsCOMPtr<nsIFile>>{})) {
186 RemoveProfileRecursion(file,
187 /* aIsIgnoreRoot */ false,
188 /* aIsIgnoreLockfile */ true,
189 undeletedFiles);
191 retries++;
195 #ifdef DEBUG
196 // XXX: Until bug 1716291 is fixed, we do not want to spam release
197 if (undeletedFiles.Length() > 0) {
198 NS_WARNING("Unable to remove all files from the profile directory:");
199 // Log the file names of those we could not remove
200 for (auto&& file : undeletedFiles) {
201 nsAutoString leafName;
202 if (NS_SUCCEEDED(file->GetLeafName(leafName))) {
203 NS_WARNING(NS_LossyConvertUTF16toASCII(leafName).get());
207 #endif
208 // XXX: Activate this assert once bug 1716291 is fixed
209 // MOZ_ASSERT(undeletedFiles.Length() == 0);
211 // Now we can unlock the profile safely.
212 lock->Unlock();
213 // nsIProfileLock is not threadsafe so release our reference to it on
214 // the main thread.
215 NS_ReleaseOnMainThread("nsToolkitProfile::RemoveProfileFiles::Unlock",
216 lock.forget());
218 if (undeletedFiles.Length() == 0) {
219 // We can safely remove the (empty) remaining profile directory
220 // and lockfile, no other files are here.
221 // As we do this only if we had no other blockers, this is as safe
222 // as deleting the lockfile explicitely after unlocking.
223 Unused << rootDir->Remove(true);
227 if (aInBackground) {
228 nsCOMPtr<nsIEventTarget> target =
229 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
230 target->Dispatch(runnable, NS_DISPATCH_NORMAL);
231 } else {
232 runnable->Run();
236 nsToolkitProfile::nsToolkitProfile(const nsACString& aName, nsIFile* aRootDir,
237 nsIFile* aLocalDir, bool aFromDB)
238 : mName(aName),
239 mRootDir(aRootDir),
240 mLocalDir(aLocalDir),
241 mLock(nullptr),
242 mIndex(0),
243 mSection("Profile") {
244 NS_ASSERTION(aRootDir, "No file!");
246 RefPtr<nsToolkitProfile> prev =
247 nsToolkitProfileService::gService->mProfiles.getLast();
248 if (prev) {
249 mIndex = prev->mIndex + 1;
251 mSection.AppendInt(mIndex);
253 nsToolkitProfileService::gService->mProfiles.insertBack(this);
255 // If this profile isn't in the database already add it.
256 if (!aFromDB) {
257 nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
258 db->SetString(mSection.get(), "Name", mName.get());
260 bool isRelative = false;
261 nsCString descriptor;
262 nsToolkitProfileService::gService->GetProfileDescriptor(this, descriptor,
263 &isRelative);
265 db->SetString(mSection.get(), "IsRelative", isRelative ? "1" : "0");
266 db->SetString(mSection.get(), "Path", descriptor.get());
270 NS_IMPL_ISUPPORTS(nsToolkitProfile, nsIToolkitProfile)
272 NS_IMETHODIMP
273 nsToolkitProfile::GetRootDir(nsIFile** aResult) {
274 NS_ADDREF(*aResult = mRootDir);
275 return NS_OK;
278 NS_IMETHODIMP
279 nsToolkitProfile::GetLocalDir(nsIFile** aResult) {
280 NS_ADDREF(*aResult = mLocalDir);
281 return NS_OK;
284 NS_IMETHODIMP
285 nsToolkitProfile::GetName(nsACString& aResult) {
286 aResult = mName;
287 return NS_OK;
290 NS_IMETHODIMP
291 nsToolkitProfile::SetName(const nsACString& aName) {
292 NS_ASSERTION(nsToolkitProfileService::gService, "Where did my service go?");
294 if (mName.Equals(aName)) {
295 return NS_OK;
298 // Changing the name from the dev-edition default profile name makes this
299 // profile no longer the dev-edition default.
300 if (mName.EqualsLiteral(DEV_EDITION_NAME) &&
301 nsToolkitProfileService::gService->mDevEditionDefault == this) {
302 nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
305 mName = aName;
307 nsresult rv = nsToolkitProfileService::gService->mProfileDB.SetString(
308 mSection.get(), "Name", mName.get());
309 NS_ENSURE_SUCCESS(rv, rv);
311 // Setting the name to the dev-edition default profile name will cause this
312 // profile to become the dev-edition default.
313 if (aName.EqualsLiteral(DEV_EDITION_NAME) &&
314 !nsToolkitProfileService::gService->mDevEditionDefault) {
315 nsToolkitProfileService::gService->mDevEditionDefault = this;
318 return NS_OK;
321 nsresult nsToolkitProfile::RemoveInternal(bool aRemoveFiles,
322 bool aInBackground) {
323 NS_ASSERTION(nsToolkitProfileService::gService, "Whoa, my service is gone.");
325 if (mLock) return NS_ERROR_FILE_IS_LOCKED;
327 if (!isInList()) {
328 return NS_ERROR_NOT_INITIALIZED;
331 if (aRemoveFiles) {
332 RemoveProfileFiles(this, aInBackground);
335 nsINIParser* db = &nsToolkitProfileService::gService->mProfileDB;
336 db->DeleteSection(mSection.get());
338 // We make some assumptions that the profile's index in the database is based
339 // on its position in the linked list. Removing a profile means we have to fix
340 // the index of later profiles in the list. The easiest way to do that is just
341 // to move the last profile into the profile's position and just update its
342 // index.
343 RefPtr<nsToolkitProfile> last =
344 nsToolkitProfileService::gService->mProfiles.getLast();
345 if (last != this) {
346 // Update the section in the db.
347 last->mIndex = mIndex;
348 db->RenameSection(last->mSection.get(), mSection.get());
349 last->mSection = mSection;
351 if (last != getNext()) {
352 last->remove();
353 setNext(last);
357 remove();
359 if (nsToolkitProfileService::gService->mNormalDefault == this) {
360 nsToolkitProfileService::gService->mNormalDefault = nullptr;
362 if (nsToolkitProfileService::gService->mDevEditionDefault == this) {
363 nsToolkitProfileService::gService->mDevEditionDefault = nullptr;
365 if (nsToolkitProfileService::gService->mDedicatedProfile == this) {
366 nsToolkitProfileService::gService->SetDefaultProfile(nullptr);
369 return NS_OK;
372 NS_IMETHODIMP
373 nsToolkitProfile::Remove(bool removeFiles) {
374 return RemoveInternal(removeFiles, false /* in background */);
377 NS_IMETHODIMP
378 nsToolkitProfile::RemoveInBackground(bool removeFiles) {
379 return RemoveInternal(removeFiles, true /* in background */);
382 NS_IMETHODIMP
383 nsToolkitProfile::Lock(nsIProfileUnlocker** aUnlocker,
384 nsIProfileLock** aResult) {
385 if (mLock) {
386 NS_ADDREF(*aResult = mLock);
387 return NS_OK;
390 RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
392 nsresult rv = lock->Init(this, aUnlocker);
393 if (NS_FAILED(rv)) return rv;
395 NS_ADDREF(*aResult = lock);
396 return NS_OK;
399 NS_IMPL_ISUPPORTS(nsToolkitProfileLock, nsIProfileLock)
401 nsresult nsToolkitProfileLock::Init(nsToolkitProfile* aProfile,
402 nsIProfileUnlocker** aUnlocker) {
403 nsresult rv;
404 rv = Init(aProfile->mRootDir, aProfile->mLocalDir, aUnlocker);
405 if (NS_SUCCEEDED(rv)) mProfile = aProfile;
407 return rv;
410 nsresult nsToolkitProfileLock::Init(nsIFile* aDirectory,
411 nsIFile* aLocalDirectory,
412 nsIProfileUnlocker** aUnlocker) {
413 nsresult rv;
415 rv = mLock.Lock(aDirectory, aUnlocker);
417 if (NS_SUCCEEDED(rv)) {
418 mDirectory = aDirectory;
419 mLocalDirectory = aLocalDirectory;
422 return rv;
425 NS_IMETHODIMP
426 nsToolkitProfileLock::GetDirectory(nsIFile** aResult) {
427 if (!mDirectory) {
428 NS_ERROR("Not initialized, or unlocked!");
429 return NS_ERROR_NOT_INITIALIZED;
432 NS_ADDREF(*aResult = mDirectory);
433 return NS_OK;
436 NS_IMETHODIMP
437 nsToolkitProfileLock::GetLocalDirectory(nsIFile** aResult) {
438 if (!mLocalDirectory) {
439 NS_ERROR("Not initialized, or unlocked!");
440 return NS_ERROR_NOT_INITIALIZED;
443 NS_ADDREF(*aResult = mLocalDirectory);
444 return NS_OK;
447 NS_IMETHODIMP
448 nsToolkitProfileLock::Unlock() {
449 if (!mDirectory) {
450 NS_ERROR("Unlocking a never-locked nsToolkitProfileLock!");
451 return NS_ERROR_UNEXPECTED;
454 // XXX If we get here with an active quota manager,
455 // something went very wrong. We want to assert this.
457 mLock.Unlock();
459 if (mProfile) {
460 mProfile->mLock = nullptr;
461 mProfile = nullptr;
463 mDirectory = nullptr;
464 mLocalDirectory = nullptr;
466 return NS_OK;
469 NS_IMETHODIMP
470 nsToolkitProfileLock::GetReplacedLockTime(PRTime* aResult) {
471 mLock.GetReplacedLockTime(aResult);
472 return NS_OK;
475 nsToolkitProfileLock::~nsToolkitProfileLock() {
476 if (mDirectory) {
477 Unlock();
481 nsToolkitProfileService* nsToolkitProfileService::gService = nullptr;
483 NS_IMPL_ISUPPORTS(nsToolkitProfileService, nsIToolkitProfileService)
485 nsToolkitProfileService::nsToolkitProfileService()
486 : mStartupProfileSelected(false),
487 mStartWithLast(true),
488 mIsFirstRun(true),
489 mUseDevEditionProfile(false),
490 #ifdef MOZ_DEDICATED_PROFILES
491 mUseDedicatedProfile(!IsSnapEnvironment() && !UseLegacyProfiles()),
492 #else
493 mUseDedicatedProfile(false),
494 #endif
495 mStartupReason(u"unknown"_ns),
496 mStartupFileVersion("0"_ns),
497 mMaybeLockProfile(false),
498 mUpdateChannel(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)),
499 mProfileDBExists(false),
500 mProfileDBFileSize(0),
501 mProfileDBModifiedTime(0) {
502 #ifdef MOZ_DEV_EDITION
503 mUseDevEditionProfile = true;
504 #endif
507 nsToolkitProfileService::~nsToolkitProfileService() {
508 gService = nullptr;
509 mProfiles.clear();
512 void nsToolkitProfileService::CompleteStartup() {
513 if (!mStartupProfileSelected) {
514 return;
517 ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_SELECTION_REASON,
518 mStartupReason);
519 ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_DATABASE_VERSION,
520 NS_ConvertUTF8toUTF16(mStartupFileVersion));
521 ScalarSet(mozilla::Telemetry::ScalarID::STARTUP_PROFILE_COUNT,
522 static_cast<uint32_t>(mProfiles.length()));
524 if (mMaybeLockProfile) {
525 nsCOMPtr<nsIToolkitShellService> shell =
526 do_GetService(NS_TOOLKITSHELLSERVICE_CONTRACTID);
527 if (!shell) {
528 return;
531 bool isDefaultApp;
532 nsresult rv = shell->IsDefaultApplication(&isDefaultApp);
533 NS_ENSURE_SUCCESS_VOID(rv);
535 if (isDefaultApp) {
536 mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
538 // There is a very small chance that this could fail if something else
539 // overwrote the profiles database since we started up, probably less than
540 // a second ago. There isn't really a sane response here, all the other
541 // profile changes are already flushed so whether we fail to flush here or
542 // force quit the app makes no difference.
543 NS_ENSURE_SUCCESS_VOID(Flush());
548 // Tests whether the passed profile was last used by this install.
549 bool nsToolkitProfileService::IsProfileForCurrentInstall(
550 nsIToolkitProfile* aProfile) {
551 nsCOMPtr<nsIFile> profileDir;
552 nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
553 NS_ENSURE_SUCCESS(rv, false);
555 nsCOMPtr<nsIFile> compatFile;
556 rv = profileDir->Clone(getter_AddRefs(compatFile));
557 NS_ENSURE_SUCCESS(rv, false);
559 rv = compatFile->Append(COMPAT_FILE);
560 NS_ENSURE_SUCCESS(rv, false);
562 nsINIParser compatData;
563 rv = compatData.Init(compatFile);
564 NS_ENSURE_SUCCESS(rv, false);
567 * In xpcshell gDirServiceProvider doesn't have all the correct directories
568 * set so using NS_GetSpecialDirectory works better there. But in a normal
569 * app launch the component registry isn't initialized so
570 * NS_GetSpecialDirectory doesn't work. So we have to use two different
571 * paths to support testing.
573 nsCOMPtr<nsIFile> currentGreDir;
574 rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(currentGreDir));
575 if (rv == NS_ERROR_NOT_INITIALIZED) {
576 currentGreDir = gDirServiceProvider->GetGREDir();
577 MOZ_ASSERT(currentGreDir, "No GRE dir found.");
578 } else if (NS_FAILED(rv)) {
579 return false;
582 nsCString lastGreDirStr;
583 rv = compatData.GetString("Compatibility", "LastPlatformDir", lastGreDirStr);
584 // If this string is missing then this profile is from an ancient version.
585 // We'll opt to use it in this case.
586 if (NS_FAILED(rv)) {
587 return true;
590 nsCOMPtr<nsIFile> lastGreDir;
591 rv = NS_NewNativeLocalFile(""_ns, false, getter_AddRefs(lastGreDir));
592 NS_ENSURE_SUCCESS(rv, false);
594 rv = lastGreDir->SetPersistentDescriptor(lastGreDirStr);
595 NS_ENSURE_SUCCESS(rv, false);
597 #ifdef XP_WIN
598 # if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE)
599 mozilla::PathString lastGreDirPath, currentGreDirPath;
600 lastGreDirPath = lastGreDir->NativePath();
601 currentGreDirPath = currentGreDir->NativePath();
602 if (lastGreDirPath.Equals(currentGreDirPath,
603 nsCaseInsensitiveStringComparator)) {
604 return true;
607 // Convert a 64-bit install path to what would have been the 32-bit install
608 // path to allow users to migrate their profiles from one to the other.
609 PWSTR pathX86 = nullptr;
610 HRESULT hres =
611 SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr, &pathX86);
612 if (SUCCEEDED(hres)) {
613 nsDependentString strPathX86(pathX86);
614 if (!StringBeginsWith(currentGreDirPath, strPathX86,
615 nsCaseInsensitiveStringComparator)) {
616 PWSTR path = nullptr;
617 hres = SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, nullptr, &path);
618 if (SUCCEEDED(hres)) {
619 if (StringBeginsWith(currentGreDirPath, nsDependentString(path),
620 nsCaseInsensitiveStringComparator)) {
621 currentGreDirPath.Replace(0, wcslen(path), strPathX86);
624 CoTaskMemFree(path);
627 CoTaskMemFree(pathX86);
629 return lastGreDirPath.Equals(currentGreDirPath,
630 nsCaseInsensitiveStringComparator);
631 # endif
632 #endif
634 bool equal;
635 rv = lastGreDir->Equals(currentGreDir, &equal);
636 NS_ENSURE_SUCCESS(rv, false);
638 return equal;
642 * Used the first time an install with dedicated profile support runs. Decides
643 * whether to mark the passed profile as the default for this install.
645 * The goal is to reduce disruption but ideally end up with the OS default
646 * install using the old default profile.
648 * If the decision is to use the profile then it will be unassigned as the
649 * dedicated default for other installs.
651 * We won't attempt to use the profile if it was last used by a different
652 * install.
654 * If the profile is currently in use by an install that was either the OS
655 * default install or the profile has been explicitely chosen by some other
656 * means then we won't use it.
658 * aResult will be set to true if we chose to make the profile the new dedicated
659 * default.
661 nsresult nsToolkitProfileService::MaybeMakeDefaultDedicatedProfile(
662 nsIToolkitProfile* aProfile, bool* aResult) {
663 nsresult rv;
664 *aResult = false;
666 // If the profile was last used by a different install then we won't use it.
667 if (!IsProfileForCurrentInstall(aProfile)) {
668 return NS_OK;
671 nsCString descriptor;
672 rv = GetProfileDescriptor(aProfile, descriptor, nullptr);
673 NS_ENSURE_SUCCESS(rv, rv);
675 // Get a list of all the installs.
676 nsTArray<nsCString> installs = GetKnownInstalls();
678 // Cache the installs that use the profile.
679 nsTArray<nsCString> inUseInstalls;
681 // See if the profile is already in use by an install that hasn't locked it.
682 for (uint32_t i = 0; i < installs.Length(); i++) {
683 const nsCString& install = installs[i];
685 nsCString path;
686 rv = mProfileDB.GetString(install.get(), "Default", path);
687 if (NS_FAILED(rv)) {
688 continue;
691 // Is this install using the profile we care about?
692 if (!descriptor.Equals(path)) {
693 continue;
696 // Is this profile locked to this other install?
697 nsCString isLocked;
698 rv = mProfileDB.GetString(install.get(), "Locked", isLocked);
699 if (NS_SUCCEEDED(rv) && isLocked.Equals("1")) {
700 return NS_OK;
703 inUseInstalls.AppendElement(install);
706 // At this point we've decided to take the profile. Strip it from other
707 // installs.
708 for (uint32_t i = 0; i < inUseInstalls.Length(); i++) {
709 // Removing the default setting entirely will make the install go through
710 // the first run process again at startup and create itself a new profile.
711 mProfileDB.DeleteString(inUseInstalls[i].get(), "Default");
714 // Set this as the default profile for this install.
715 SetDefaultProfile(aProfile);
717 // SetDefaultProfile will have locked this profile to this install so no
718 // other installs will steal it, but this was auto-selected so we want to
719 // unlock it so that other installs can potentially take it.
720 mProfileDB.DeleteString(mInstallSection.get(), "Locked");
722 // Persist the changes.
723 rv = Flush();
724 NS_ENSURE_SUCCESS(rv, rv);
726 // Once XPCOM is available check if this is the default application and if so
727 // lock the profile again.
728 mMaybeLockProfile = true;
729 *aResult = true;
731 return NS_OK;
734 bool IsFileOutdated(nsIFile* aFile, bool aExists, PRTime aLastModified,
735 int64_t aLastSize) {
736 nsCOMPtr<nsIFile> file;
737 nsresult rv = aFile->Clone(getter_AddRefs(file));
738 if (NS_FAILED(rv)) {
739 return false;
742 bool exists;
743 rv = aFile->Exists(&exists);
744 if (NS_FAILED(rv) || exists != aExists) {
745 return true;
748 if (!exists) {
749 return false;
752 int64_t size;
753 rv = aFile->GetFileSize(&size);
754 if (NS_FAILED(rv) || size != aLastSize) {
755 return true;
758 PRTime time;
759 rv = aFile->GetLastModifiedTime(&time);
760 if (NS_FAILED(rv) || time != aLastModified) {
761 return true;
764 return false;
767 nsresult UpdateFileStats(nsIFile* aFile, bool* aExists, PRTime* aLastModified,
768 int64_t* aLastSize) {
769 nsCOMPtr<nsIFile> file;
770 nsresult rv = aFile->Clone(getter_AddRefs(file));
771 NS_ENSURE_SUCCESS(rv, rv);
773 rv = file->Exists(aExists);
774 NS_ENSURE_SUCCESS(rv, rv);
776 if (!(*aExists)) {
777 *aLastModified = 0;
778 *aLastSize = 0;
779 return NS_OK;
782 rv = file->GetFileSize(aLastSize);
783 NS_ENSURE_SUCCESS(rv, rv);
785 rv = file->GetLastModifiedTime(aLastModified);
786 NS_ENSURE_SUCCESS(rv, rv);
788 return NS_OK;
791 NS_IMETHODIMP
792 nsToolkitProfileService::GetIsListOutdated(bool* aResult) {
793 if (IsFileOutdated(mProfileDBFile, mProfileDBExists, mProfileDBModifiedTime,
794 mProfileDBFileSize)) {
795 *aResult = true;
796 return NS_OK;
799 *aResult = false;
800 return NS_OK;
803 struct ImportInstallsClosure {
804 nsINIParser* backupData;
805 nsINIParser* profileDB;
808 static bool ImportInstalls(const char* aSection, void* aClosure) {
809 ImportInstallsClosure* closure =
810 static_cast<ImportInstallsClosure*>(aClosure);
812 nsTArray<UniquePtr<KeyValue>> strings =
813 GetSectionStrings(closure->backupData, aSection);
814 if (strings.IsEmpty()) {
815 return true;
818 nsCString newSection(INSTALL_PREFIX);
819 newSection.Append(aSection);
820 nsCString buffer;
822 for (uint32_t i = 0; i < strings.Length(); i++) {
823 closure->profileDB->SetString(newSection.get(), strings[i]->key.get(),
824 strings[i]->value.get());
827 return true;
830 nsresult nsToolkitProfileService::Init() {
831 NS_ASSERTION(gDirServiceProvider, "No dirserviceprovider!");
832 nsresult rv;
834 rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(mAppData));
835 NS_ENSURE_SUCCESS(rv, rv);
837 rv = nsXREDirProvider::GetUserLocalDataDirectory(getter_AddRefs(mTempData));
838 NS_ENSURE_SUCCESS(rv, rv);
840 rv = mAppData->Clone(getter_AddRefs(mProfileDBFile));
841 NS_ENSURE_SUCCESS(rv, rv);
843 rv = mProfileDBFile->AppendNative("profiles.ini"_ns);
844 NS_ENSURE_SUCCESS(rv, rv);
846 rv = mAppData->Clone(getter_AddRefs(mInstallDBFile));
847 NS_ENSURE_SUCCESS(rv, rv);
849 rv = mInstallDBFile->AppendNative("installs.ini"_ns);
850 NS_ENSURE_SUCCESS(rv, rv);
852 nsAutoCString buffer;
854 rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
855 &mProfileDBModifiedTime, &mProfileDBFileSize);
856 if (NS_SUCCEEDED(rv) && mProfileDBExists) {
857 rv = mProfileDB.Init(mProfileDBFile);
858 // Init does not fail on parsing errors, only on OOM/really unexpected
859 // conditions.
860 if (NS_FAILED(rv)) {
861 return rv;
864 rv = mProfileDB.GetString("General", "StartWithLastProfile", buffer);
865 if (NS_SUCCEEDED(rv)) {
866 mStartWithLast = !buffer.EqualsLiteral("0");
869 rv = mProfileDB.GetString("General", "Version", mStartupFileVersion);
870 if (NS_FAILED(rv)) {
871 // This is a profiles.ini written by an older version. We must restore
872 // any install data from the backup. We consider this old format to be
873 // a version 1 file.
874 mStartupFileVersion.AssignLiteral("1");
875 nsINIParser installDB;
877 if (NS_SUCCEEDED(installDB.Init(mInstallDBFile))) {
878 // There is install data to import.
879 ImportInstallsClosure closure = {&installDB, &mProfileDB};
880 installDB.GetSections(&ImportInstalls, &closure);
883 rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
884 NS_ENSURE_SUCCESS(rv, rv);
886 } else {
887 rv = mProfileDB.SetString("General", "StartWithLastProfile",
888 mStartWithLast ? "1" : "0");
889 NS_ENSURE_SUCCESS(rv, rv);
890 rv = mProfileDB.SetString("General", "Version", PROFILE_DB_VERSION);
891 NS_ENSURE_SUCCESS(rv, rv);
894 nsCString installProfilePath;
896 if (mUseDedicatedProfile) {
897 nsString installHash;
898 rv = gDirServiceProvider->GetInstallHash(installHash);
899 NS_ENSURE_SUCCESS(rv, rv);
900 CopyUTF16toUTF8(installHash, mInstallSection);
901 mInstallSection.Insert(INSTALL_PREFIX, 0);
903 // Try to find the descriptor for the default profile for this install.
904 rv = mProfileDB.GetString(mInstallSection.get(), "Default",
905 installProfilePath);
907 // Not having a value means this install doesn't appear in installs.ini so
908 // this is the first run for this install.
909 if (NS_FAILED(rv)) {
910 mIsFirstRun = true;
912 // Gets the install section that would have been created if the install
913 // path has incorrect casing (see bug 1555319). We use this later during
914 // profile selection.
915 rv = gDirServiceProvider->GetLegacyInstallHash(installHash);
916 NS_ENSURE_SUCCESS(rv, rv);
917 CopyUTF16toUTF8(installHash, mLegacyInstallSection);
918 mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
919 } else {
920 mIsFirstRun = false;
924 nsToolkitProfile* currentProfile = nullptr;
926 #ifdef MOZ_DEV_EDITION
927 nsCOMPtr<nsIFile> ignoreDevEditionProfile;
928 rv = mAppData->Clone(getter_AddRefs(ignoreDevEditionProfile));
929 if (NS_FAILED(rv)) {
930 return rv;
933 rv = ignoreDevEditionProfile->AppendNative("ignore-dev-edition-profile"_ns);
934 if (NS_FAILED(rv)) {
935 return rv;
938 bool shouldIgnoreSeparateProfile;
939 rv = ignoreDevEditionProfile->Exists(&shouldIgnoreSeparateProfile);
940 if (NS_FAILED(rv)) return rv;
942 mUseDevEditionProfile = !shouldIgnoreSeparateProfile;
943 #endif
945 nsCOMPtr<nsIToolkitProfile> autoSelectProfile;
947 unsigned int nonDevEditionProfiles = 0;
948 unsigned int c = 0;
949 for (c = 0; true; ++c) {
950 nsAutoCString profileID("Profile");
951 profileID.AppendInt(c);
953 rv = mProfileDB.GetString(profileID.get(), "IsRelative", buffer);
954 if (NS_FAILED(rv)) break;
956 bool isRelative = buffer.EqualsLiteral("1");
958 nsAutoCString filePath;
960 rv = mProfileDB.GetString(profileID.get(), "Path", filePath);
961 if (NS_FAILED(rv)) {
962 NS_ERROR("Malformed profiles.ini: Path= not found");
963 continue;
966 nsAutoCString name;
968 rv = mProfileDB.GetString(profileID.get(), "Name", name);
969 if (NS_FAILED(rv)) {
970 NS_ERROR("Malformed profiles.ini: Name= not found");
971 continue;
974 nsCOMPtr<nsIFile> rootDir;
975 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(rootDir));
976 NS_ENSURE_SUCCESS(rv, rv);
978 if (isRelative) {
979 rv = rootDir->SetRelativeDescriptor(mAppData, filePath);
980 } else {
981 rv = rootDir->SetPersistentDescriptor(filePath);
983 if (NS_FAILED(rv)) continue;
985 nsCOMPtr<nsIFile> localDir;
986 if (isRelative) {
987 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localDir));
988 NS_ENSURE_SUCCESS(rv, rv);
990 rv = localDir->SetRelativeDescriptor(mTempData, filePath);
991 } else {
992 localDir = rootDir;
995 currentProfile = new nsToolkitProfile(name, rootDir, localDir, true);
997 // If a user has modified the ini file path it may make for a valid profile
998 // path but not match what we would have serialised and so may not match
999 // the path in the install section. Re-serialise it to get it in the
1000 // expected form again.
1001 bool nowRelative;
1002 nsCString descriptor;
1003 GetProfileDescriptor(currentProfile, descriptor, &nowRelative);
1005 if (isRelative != nowRelative || !descriptor.Equals(filePath)) {
1006 mProfileDB.SetString(profileID.get(), "IsRelative",
1007 nowRelative ? "1" : "0");
1008 mProfileDB.SetString(profileID.get(), "Path", descriptor.get());
1010 // Should we flush now? It costs some startup time and we will fix it on
1011 // the next startup anyway. If something else causes a flush then it will
1012 // be fixed in the ini file then.
1015 rv = mProfileDB.GetString(profileID.get(), "Default", buffer);
1016 if (NS_SUCCEEDED(rv) && buffer.EqualsLiteral("1")) {
1017 mNormalDefault = currentProfile;
1020 // Is this the default profile for this install?
1021 if (mUseDedicatedProfile && !mDedicatedProfile &&
1022 installProfilePath.Equals(descriptor)) {
1023 // Found a profile for this install.
1024 mDedicatedProfile = currentProfile;
1027 if (name.EqualsLiteral(DEV_EDITION_NAME)) {
1028 mDevEditionDefault = currentProfile;
1029 } else {
1030 nonDevEditionProfiles++;
1031 autoSelectProfile = currentProfile;
1035 // If there is only one non-dev-edition profile then mark it as the default.
1036 if (!mNormalDefault && nonDevEditionProfiles == 1) {
1037 SetNormalDefault(autoSelectProfile);
1040 if (!mUseDedicatedProfile) {
1041 if (mUseDevEditionProfile) {
1042 // When using the separate dev-edition profile not finding it means this
1043 // is a first run.
1044 mIsFirstRun = !mDevEditionDefault;
1045 } else {
1046 // If there are no normal profiles then this is a first run.
1047 mIsFirstRun = nonDevEditionProfiles == 0;
1051 return NS_OK;
1054 NS_IMETHODIMP
1055 nsToolkitProfileService::SetStartWithLastProfile(bool aValue) {
1056 if (mStartWithLast != aValue) {
1057 // Note: the skeleton ui (see PreXULSkeletonUI.cpp) depends on this
1058 // having this name and being under General. If that ever changes,
1059 // the skeleton UI will just need to be updated. If it changes frequently,
1060 // it's probably best we just mirror the value to the registry here.
1061 nsresult rv = mProfileDB.SetString("General", "StartWithLastProfile",
1062 aValue ? "1" : "0");
1063 NS_ENSURE_SUCCESS(rv, rv);
1064 mStartWithLast = aValue;
1066 return NS_OK;
1069 NS_IMETHODIMP
1070 nsToolkitProfileService::GetStartWithLastProfile(bool* aResult) {
1071 *aResult = mStartWithLast;
1072 return NS_OK;
1075 NS_IMETHODIMP
1076 nsToolkitProfileService::GetProfiles(nsISimpleEnumerator** aResult) {
1077 *aResult = new ProfileEnumerator(mProfiles.getFirst());
1079 NS_ADDREF(*aResult);
1080 return NS_OK;
1083 NS_IMETHODIMP
1084 nsToolkitProfileService::ProfileEnumerator::HasMoreElements(bool* aResult) {
1085 *aResult = mCurrent ? true : false;
1086 return NS_OK;
1089 NS_IMETHODIMP
1090 nsToolkitProfileService::ProfileEnumerator::GetNext(nsISupports** aResult) {
1091 if (!mCurrent) return NS_ERROR_FAILURE;
1093 NS_ADDREF(*aResult = mCurrent);
1095 mCurrent = mCurrent->getNext();
1096 return NS_OK;
1099 NS_IMETHODIMP
1100 nsToolkitProfileService::GetCurrentProfile(nsIToolkitProfile** aResult) {
1101 NS_IF_ADDREF(*aResult = mCurrent);
1102 return NS_OK;
1105 NS_IMETHODIMP
1106 nsToolkitProfileService::GetDefaultProfile(nsIToolkitProfile** aResult) {
1107 if (mUseDedicatedProfile) {
1108 NS_IF_ADDREF(*aResult = mDedicatedProfile);
1109 return NS_OK;
1112 if (mUseDevEditionProfile) {
1113 NS_IF_ADDREF(*aResult = mDevEditionDefault);
1114 return NS_OK;
1117 NS_IF_ADDREF(*aResult = mNormalDefault);
1118 return NS_OK;
1121 void nsToolkitProfileService::SetNormalDefault(nsIToolkitProfile* aProfile) {
1122 if (mNormalDefault == aProfile) {
1123 return;
1126 if (mNormalDefault) {
1127 nsToolkitProfile* profile =
1128 static_cast<nsToolkitProfile*>(mNormalDefault.get());
1129 mProfileDB.DeleteString(profile->mSection.get(), "Default");
1132 mNormalDefault = aProfile;
1134 if (mNormalDefault) {
1135 nsToolkitProfile* profile =
1136 static_cast<nsToolkitProfile*>(mNormalDefault.get());
1137 mProfileDB.SetString(profile->mSection.get(), "Default", "1");
1141 NS_IMETHODIMP
1142 nsToolkitProfileService::SetDefaultProfile(nsIToolkitProfile* aProfile) {
1143 if (mUseDedicatedProfile) {
1144 if (mDedicatedProfile != aProfile) {
1145 if (!aProfile) {
1146 // Setting this to the empty string means no profile will be found on
1147 // startup but we'll recognise that this install has been used
1148 // previously.
1149 mProfileDB.SetString(mInstallSection.get(), "Default", "");
1150 } else {
1151 nsCString profilePath;
1152 nsresult rv = GetProfileDescriptor(aProfile, profilePath, nullptr);
1153 NS_ENSURE_SUCCESS(rv, rv);
1155 mProfileDB.SetString(mInstallSection.get(), "Default",
1156 profilePath.get());
1158 mDedicatedProfile = aProfile;
1160 // Some kind of choice has happened here, lock this profile to this
1161 // install.
1162 mProfileDB.SetString(mInstallSection.get(), "Locked", "1");
1164 return NS_OK;
1167 if (mUseDevEditionProfile && aProfile != mDevEditionDefault) {
1168 // The separate profile is hardcoded.
1169 return NS_ERROR_FAILURE;
1172 SetNormalDefault(aProfile);
1174 return NS_OK;
1177 // Gets the profile root directory descriptor for storing in profiles.ini or
1178 // installs.ini.
1179 nsresult nsToolkitProfileService::GetProfileDescriptor(
1180 nsIToolkitProfile* aProfile, nsACString& aDescriptor, bool* aIsRelative) {
1181 nsCOMPtr<nsIFile> profileDir;
1182 nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
1183 NS_ENSURE_SUCCESS(rv, rv);
1185 // if the profile dir is relative to appdir...
1186 bool isRelative;
1187 rv = mAppData->Contains(profileDir, &isRelative);
1189 nsCString profilePath;
1190 if (NS_SUCCEEDED(rv) && isRelative) {
1191 // we use a relative descriptor
1192 rv = profileDir->GetRelativeDescriptor(mAppData, profilePath);
1193 } else {
1194 // otherwise, a persistent descriptor
1195 rv = profileDir->GetPersistentDescriptor(profilePath);
1197 NS_ENSURE_SUCCESS(rv, rv);
1199 aDescriptor.Assign(profilePath);
1200 if (aIsRelative) {
1201 *aIsRelative = isRelative;
1204 return NS_OK;
1207 nsresult nsToolkitProfileService::CreateDefaultProfile(
1208 nsIToolkitProfile** aResult) {
1209 // Create a new default profile
1210 nsAutoCString name;
1211 if (mUseDevEditionProfile) {
1212 name.AssignLiteral(DEV_EDITION_NAME);
1213 } else if (mUseDedicatedProfile) {
1214 name.AppendPrintf("default-%s", mUpdateChannel.get());
1215 } else {
1216 name.AssignLiteral(DEFAULT_NAME);
1219 nsresult rv = CreateUniqueProfile(nullptr, name, aResult);
1220 NS_ENSURE_SUCCESS(rv, rv);
1222 if (mUseDedicatedProfile) {
1223 SetDefaultProfile(mCurrent);
1224 } else if (mUseDevEditionProfile) {
1225 mDevEditionDefault = mCurrent;
1226 } else {
1227 SetNormalDefault(mCurrent);
1230 return NS_OK;
1234 * An implementation of SelectStartupProfile callable from JavaScript via XPCOM.
1235 * See nsIToolkitProfileService.idl.
1237 NS_IMETHODIMP
1238 nsToolkitProfileService::SelectStartupProfile(
1239 const nsTArray<nsCString>& aArgv, bool aIsResetting,
1240 const nsACString& aUpdateChannel, const nsACString& aLegacyInstallHash,
1241 nsIFile** aRootDir, nsIFile** aLocalDir, nsIToolkitProfile** aProfile,
1242 bool* aDidCreate) {
1243 int argc = aArgv.Length();
1244 // Our command line handling expects argv to be null-terminated so construct
1245 // an appropriate array.
1246 auto argv = MakeUnique<char*[]>(argc + 1);
1247 // Also, our command line handling removes things from the array without
1248 // freeing them so keep track of what we've created separately.
1249 auto allocated = MakeUnique<UniqueFreePtr<char>[]>(argc);
1251 for (int i = 0; i < argc; i++) {
1252 allocated[i].reset(ToNewCString(aArgv[i]));
1253 argv[i] = allocated[i].get();
1255 argv[argc] = nullptr;
1257 mUpdateChannel = aUpdateChannel;
1258 if (!aLegacyInstallHash.IsEmpty()) {
1259 mLegacyInstallSection.Assign(aLegacyInstallHash);
1260 mLegacyInstallSection.Insert(INSTALL_PREFIX, 0);
1263 bool wasDefault;
1264 nsresult rv =
1265 SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
1266 aProfile, aDidCreate, &wasDefault);
1268 // Since we were called outside of the normal startup path complete any
1269 // startup tasks.
1270 if (NS_SUCCEEDED(rv)) {
1271 CompleteStartup();
1274 return rv;
1277 static void SaltProfileName(nsACString& aName);
1280 * Selects or creates a profile to use based on the profiles database, any
1281 * environment variables and any command line arguments. Will not create
1282 * a profile if aIsResetting is true. The profile is selected based on this
1283 * order of preference:
1284 * * Environment variables (set when restarting the application).
1285 * * --profile command line argument.
1286 * * --createprofile command line argument (this also causes the app to exit).
1287 * * -p command line argument.
1288 * * A new profile created if this is the first run of the application.
1289 * * The default profile.
1290 * aRootDir and aLocalDir are set to the data and local directories for the
1291 * profile data. If a profile from the database was selected it will be
1292 * returned in aProfile.
1293 * aDidCreate will be set to true if a new profile was created.
1294 * This function should be called once at startup and will fail if called again.
1295 * aArgv should be an array of aArgc + 1 strings, the last element being null.
1296 * Both aArgv and aArgc will be mutated.
1298 nsresult nsToolkitProfileService::SelectStartupProfile(
1299 int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
1300 nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate,
1301 bool* aWasDefaultSelection) {
1302 if (mStartupProfileSelected) {
1303 return NS_ERROR_ALREADY_INITIALIZED;
1306 mStartupProfileSelected = true;
1307 *aDidCreate = false;
1308 *aWasDefaultSelection = false;
1310 nsresult rv;
1311 const char* arg;
1313 // Use the profile specified in the environment variables (generally from an
1314 // app initiated restart).
1315 nsCOMPtr<nsIFile> lf = GetFileFromEnv("XRE_PROFILE_PATH");
1316 if (lf) {
1317 nsCOMPtr<nsIFile> localDir = GetFileFromEnv("XRE_PROFILE_LOCAL_PATH");
1318 if (!localDir) {
1319 localDir = lf;
1322 // Clear out flags that we handled (or should have handled!) last startup.
1323 const char* dummy;
1324 CheckArg(*aArgc, aArgv, "p", &dummy);
1325 CheckArg(*aArgc, aArgv, "profile", &dummy);
1326 CheckArg(*aArgc, aArgv, "profilemanager");
1328 nsCOMPtr<nsIToolkitProfile> profile;
1329 GetProfileByDir(lf, localDir, getter_AddRefs(profile));
1331 if (profile && mIsFirstRun && mUseDedicatedProfile) {
1332 if (profile ==
1333 (mUseDevEditionProfile ? mDevEditionDefault : mNormalDefault)) {
1334 // This is the first run of a dedicated profile build where the selected
1335 // profile is the previous default so we should either make it the
1336 // default profile for this install or push the user to a new profile.
1338 bool result;
1339 rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
1340 NS_ENSURE_SUCCESS(rv, rv);
1341 if (result) {
1342 mStartupReason = u"restart-claimed-default"_ns;
1344 mCurrent = profile;
1345 } else {
1346 rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
1347 if (NS_FAILED(rv)) {
1348 *aProfile = nullptr;
1349 return rv;
1352 rv = Flush();
1353 NS_ENSURE_SUCCESS(rv, rv);
1355 mStartupReason = u"restart-skipped-default"_ns;
1356 *aDidCreate = true;
1359 NS_IF_ADDREF(*aProfile = mCurrent);
1360 mCurrent->GetRootDir(aRootDir);
1361 mCurrent->GetLocalDir(aLocalDir);
1363 return NS_OK;
1367 if (EnvHasValue("XRE_RESTARTED_BY_PROFILE_MANAGER")) {
1368 mStartupReason = u"profile-manager"_ns;
1369 } else if (aIsResetting) {
1370 mStartupReason = u"profile-reset"_ns;
1371 } else {
1372 mStartupReason = u"restart"_ns;
1375 mCurrent = profile;
1376 lf.forget(aRootDir);
1377 localDir.forget(aLocalDir);
1378 NS_IF_ADDREF(*aProfile = profile);
1379 return NS_OK;
1382 // Check the -profile command line argument. It accepts a single argument that
1383 // gives the path to use for the profile.
1384 ArgResult ar = CheckArg(*aArgc, aArgv, "profile", &arg);
1385 if (ar == ARG_BAD) {
1386 PR_fprintf(PR_STDERR, "Error: argument --profile requires a path\n");
1387 return NS_ERROR_FAILURE;
1389 if (ar) {
1390 nsCOMPtr<nsIFile> lf;
1391 rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
1392 NS_ENSURE_SUCCESS(rv, rv);
1394 // Make sure that the profile path exists and it's a directory.
1395 bool exists;
1396 rv = lf->Exists(&exists);
1397 NS_ENSURE_SUCCESS(rv, rv);
1398 if (!exists) {
1399 rv = lf->Create(nsIFile::DIRECTORY_TYPE, 0700);
1400 NS_ENSURE_SUCCESS(rv, rv);
1401 } else {
1402 bool isDir;
1403 rv = lf->IsDirectory(&isDir);
1404 NS_ENSURE_SUCCESS(rv, rv);
1405 if (!isDir) {
1406 PR_fprintf(
1407 PR_STDERR,
1408 "Error: argument --profile requires a path to a directory\n");
1409 return NS_ERROR_FAILURE;
1413 mStartupReason = u"argument-profile"_ns;
1415 GetProfileByDir(lf, nullptr, getter_AddRefs(mCurrent));
1416 NS_ADDREF(*aRootDir = lf);
1417 // If the root dir matched a profile then use its local dir, otherwise use
1418 // the root dir as the local dir.
1419 if (mCurrent) {
1420 mCurrent->GetLocalDir(aLocalDir);
1421 } else {
1422 lf.forget(aLocalDir);
1425 NS_IF_ADDREF(*aProfile = mCurrent);
1426 return NS_OK;
1429 // Check the -createprofile command line argument. It accepts a single
1430 // argument that is either the name for the new profile or the name followed
1431 // by the path to use.
1432 ar = CheckArg(*aArgc, aArgv, "createprofile", &arg, CheckArgFlag::RemoveArg);
1433 if (ar == ARG_BAD) {
1434 PR_fprintf(PR_STDERR,
1435 "Error: argument --createprofile requires a profile name\n");
1436 return NS_ERROR_FAILURE;
1438 if (ar) {
1439 const char* delim = strchr(arg, ' ');
1440 nsCOMPtr<nsIToolkitProfile> profile;
1441 if (delim) {
1442 nsCOMPtr<nsIFile> lf;
1443 rv = NS_NewNativeLocalFile(nsDependentCString(delim + 1), true,
1444 getter_AddRefs(lf));
1445 if (NS_FAILED(rv)) {
1446 PR_fprintf(PR_STDERR, "Error: profile path not valid.\n");
1447 return rv;
1450 // As with --profile, assume that the given path will be used for the
1451 // main profile directory.
1452 rv = CreateProfile(lf, nsDependentCSubstring(arg, delim),
1453 getter_AddRefs(profile));
1454 } else {
1455 rv = CreateProfile(nullptr, nsDependentCString(arg),
1456 getter_AddRefs(profile));
1458 // Some pathological arguments can make it this far
1459 if (NS_FAILED(rv) || NS_FAILED(Flush())) {
1460 PR_fprintf(PR_STDERR, "Error creating profile.\n");
1462 return NS_ERROR_ABORT;
1465 // Check the -p command line argument. It either accepts a profile name and
1466 // uses that named profile or without a name it opens the profile manager.
1467 ar = CheckArg(*aArgc, aArgv, "p", &arg);
1468 if (ar == ARG_BAD) {
1469 return NS_ERROR_SHOW_PROFILE_MANAGER;
1471 if (ar) {
1472 rv = GetProfileByName(nsDependentCString(arg), getter_AddRefs(mCurrent));
1473 if (NS_SUCCEEDED(rv)) {
1474 mStartupReason = u"argument-p"_ns;
1476 mCurrent->GetRootDir(aRootDir);
1477 mCurrent->GetLocalDir(aLocalDir);
1479 NS_ADDREF(*aProfile = mCurrent);
1480 return NS_OK;
1483 return NS_ERROR_SHOW_PROFILE_MANAGER;
1486 ar = CheckArg(*aArgc, aArgv, "profilemanager");
1487 if (ar == ARG_FOUND) {
1488 return NS_ERROR_SHOW_PROFILE_MANAGER;
1491 #ifdef MOZ_BACKGROUNDTASKS
1492 if (BackgroundTasks::IsBackgroundTaskMode()) {
1493 // There are two cases:
1494 // 1. ephemeral profile: create a new one in temporary directory.
1495 // 2. non-ephemeral (persistent) profile:
1496 // a. if no salted profile is known, create a new one in
1497 // background task-specific directory.
1498 // b. if salted profile is know, use salted path.
1499 nsString installHash;
1500 rv = gDirServiceProvider->GetInstallHash(installHash);
1501 NS_ENSURE_SUCCESS(rv, rv);
1503 nsCString profilePrefix(BackgroundTasks::GetProfilePrefix(
1504 NS_LossyConvertUTF16toASCII(installHash)));
1506 nsCString taskName(BackgroundTasks::GetBackgroundTasks().ref());
1508 nsCOMPtr<nsIFile> file;
1510 if (BackgroundTasks::IsEphemeralProfileTaskName(taskName)) {
1511 // Background task mode does not enable legacy telemetry, so this is for
1512 // completeness and testing only.
1513 mStartupReason = u"backgroundtask-ephemeral"_ns;
1515 nsCOMPtr<nsIFile> rootDir;
1516 rv = GetSpecialSystemDirectory(OS_TemporaryDirectory,
1517 getter_AddRefs(rootDir));
1518 NS_ENSURE_SUCCESS(rv, rv);
1520 nsresult rv = BackgroundTasks::CreateEphemeralProfileDirectory(
1521 rootDir, profilePrefix, getter_AddRefs(file));
1522 if (NS_WARN_IF(NS_FAILED(rv))) {
1523 // In background task mode, NS_ERROR_UNEXPECTED is handled specially to
1524 // exit with a non-zero exit code.
1525 return NS_ERROR_UNEXPECTED;
1527 *aDidCreate = true;
1528 } else {
1529 // Background task mode does not enable legacy telemetry, so this is for
1530 // completeness and testing only.
1531 mStartupReason = u"backgroundtask-not-ephemeral"_ns;
1533 // A non-ephemeral profile is required.
1534 nsCOMPtr<nsIFile> rootDir;
1535 nsresult rv = gDirServiceProvider->GetBackgroundTasksProfilesRootDir(
1536 getter_AddRefs(rootDir));
1537 NS_ENSURE_SUCCESS(rv, rv);
1539 nsAutoCString buffer;
1540 rv = mProfileDB.GetString("BackgroundTasksProfiles", profilePrefix.get(),
1541 buffer);
1542 if (NS_SUCCEEDED(rv)) {
1543 // We have a record of one! Use it.
1544 rv = rootDir->Clone(getter_AddRefs(file));
1545 NS_ENSURE_SUCCESS(rv, rv);
1547 rv = file->AppendNative(buffer);
1548 NS_ENSURE_SUCCESS(rv, rv);
1549 } else {
1550 nsCString saltedProfilePrefix = profilePrefix;
1551 SaltProfileName(saltedProfilePrefix);
1553 nsresult rv = BackgroundTasks::CreateNonEphemeralProfileDirectory(
1554 rootDir, saltedProfilePrefix, getter_AddRefs(file));
1555 if (NS_WARN_IF(NS_FAILED(rv))) {
1556 // In background task mode, NS_ERROR_UNEXPECTED is handled specially
1557 // to exit with a non-zero exit code.
1558 return NS_ERROR_UNEXPECTED;
1560 *aDidCreate = true;
1562 // Keep a record of the salted name. It's okay if this doesn't succeed:
1563 // not great, but it's better for tasks (particularly,
1564 // `backgroundupdate`) to run and not persist state correctly than to
1565 // not run at all.
1566 rv =
1567 mProfileDB.SetString("BackgroundTasksProfiles", profilePrefix.get(),
1568 saltedProfilePrefix.get());
1569 Unused << NS_WARN_IF(NS_FAILED(rv));
1571 if (NS_SUCCEEDED(rv)) {
1572 rv = Flush();
1573 Unused << NS_WARN_IF(NS_FAILED(rv));
1578 nsCOMPtr<nsIFile> localDir = file;
1579 file.forget(aRootDir);
1580 localDir.forget(aLocalDir);
1582 // Background tasks never use profiles known to the profile service.
1583 *aProfile = nullptr;
1585 return NS_OK;
1587 #endif
1589 if (mIsFirstRun && mUseDedicatedProfile &&
1590 !mInstallSection.Equals(mLegacyInstallSection)) {
1591 // The default profile could be assigned to a hash generated from an
1592 // incorrectly cased version of the installation directory (see bug
1593 // 1555319). Ideally we'd do all this while loading profiles.ini but we
1594 // can't override the legacy section value before that for tests.
1595 nsCString defaultDescriptor;
1596 rv = mProfileDB.GetString(mLegacyInstallSection.get(), "Default",
1597 defaultDescriptor);
1599 if (NS_SUCCEEDED(rv)) {
1600 // There is a default here, need to see if it matches any profiles.
1601 bool isRelative;
1602 nsCString descriptor;
1604 for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1605 GetProfileDescriptor(profile, descriptor, &isRelative);
1607 if (descriptor.Equals(defaultDescriptor)) {
1608 // Found the default profile. Copy the install section over to
1609 // the correct location. We leave the old info in place for older
1610 // versions of Firefox to use.
1611 nsTArray<UniquePtr<KeyValue>> strings =
1612 GetSectionStrings(&mProfileDB, mLegacyInstallSection.get());
1613 for (const auto& kv : strings) {
1614 mProfileDB.SetString(mInstallSection.get(), kv->key.get(),
1615 kv->value.get());
1618 // Flush now. This causes a small blip in startup but it should be
1619 // one time only whereas not flushing means we have to do this search
1620 // on every startup.
1621 Flush();
1623 // Now start up with the found profile.
1624 mDedicatedProfile = profile;
1625 mIsFirstRun = false;
1626 break;
1632 // If this is a first run then create a new profile.
1633 if (mIsFirstRun) {
1634 // If we're configured to always show the profile manager then don't create
1635 // a new profile to use.
1636 if (!mStartWithLast) {
1637 return NS_ERROR_SHOW_PROFILE_MANAGER;
1640 bool skippedDefaultProfile = false;
1642 if (mUseDedicatedProfile) {
1643 // This is the first run of a dedicated profile install. We have to decide
1644 // whether to use the default profile used by non-dedicated-profile
1645 // installs or to create a new profile.
1647 // Find what would have been the default profile for old installs.
1648 nsCOMPtr<nsIToolkitProfile> profile = mNormalDefault;
1649 if (mUseDevEditionProfile) {
1650 profile = mDevEditionDefault;
1653 if (profile) {
1654 nsCOMPtr<nsIFile> rootDir;
1655 profile->GetRootDir(getter_AddRefs(rootDir));
1657 nsCOMPtr<nsIFile> compat;
1658 rootDir->Clone(getter_AddRefs(compat));
1659 compat->Append(COMPAT_FILE);
1661 bool exists;
1662 rv = compat->Exists(&exists);
1663 NS_ENSURE_SUCCESS(rv, rv);
1665 // If the file is missing then either this is an empty profile (likely
1666 // generated by bug 1518591) or it is from an ancient version. We'll opt
1667 // to leave it for older versions in this case.
1668 if (exists) {
1669 bool result;
1670 rv = MaybeMakeDefaultDedicatedProfile(profile, &result);
1671 NS_ENSURE_SUCCESS(rv, rv);
1672 if (result) {
1673 mStartupReason = u"firstrun-claimed-default"_ns;
1675 mCurrent = profile;
1676 rootDir.forget(aRootDir);
1677 profile->GetLocalDir(aLocalDir);
1678 profile.forget(aProfile);
1679 return NS_OK;
1682 // We're going to create a new profile for this install even though
1683 // another default exists.
1684 skippedDefaultProfile = true;
1689 rv = CreateDefaultProfile(getter_AddRefs(mCurrent));
1690 if (NS_SUCCEEDED(rv)) {
1691 #ifdef MOZ_CREATE_LEGACY_PROFILE
1692 // If there is only one profile and it isn't meant to be the profile that
1693 // older versions of Firefox use then we must create a default profile
1694 // for older versions of Firefox to avoid the existing profile being
1695 // auto-selected.
1696 if ((mUseDedicatedProfile || mUseDevEditionProfile) &&
1697 mProfiles.getFirst() == mProfiles.getLast()) {
1698 nsCOMPtr<nsIToolkitProfile> newProfile;
1699 CreateProfile(nullptr, nsLiteralCString(DEFAULT_NAME),
1700 getter_AddRefs(newProfile));
1701 SetNormalDefault(newProfile);
1703 #endif
1705 rv = Flush();
1706 NS_ENSURE_SUCCESS(rv, rv);
1708 if (skippedDefaultProfile) {
1709 mStartupReason = u"firstrun-skipped-default"_ns;
1710 } else {
1711 mStartupReason = u"firstrun-created-default"_ns;
1714 // Use the new profile.
1715 mCurrent->GetRootDir(aRootDir);
1716 mCurrent->GetLocalDir(aLocalDir);
1717 NS_ADDREF(*aProfile = mCurrent);
1719 *aDidCreate = true;
1720 return NS_OK;
1724 GetDefaultProfile(getter_AddRefs(mCurrent));
1726 // None of the profiles was marked as default (generally only happens if the
1727 // user modifies profiles.ini manually). Let the user choose.
1728 if (!mCurrent) {
1729 return NS_ERROR_SHOW_PROFILE_MANAGER;
1732 // Let the caller know that the profile was selected by default.
1733 *aWasDefaultSelection = true;
1734 mStartupReason = u"default"_ns;
1736 // Use the selected profile.
1737 mCurrent->GetRootDir(aRootDir);
1738 mCurrent->GetLocalDir(aLocalDir);
1739 NS_ADDREF(*aProfile = mCurrent);
1741 return NS_OK;
1745 * Creates a new profile for reset and mark it as the current profile.
1747 nsresult nsToolkitProfileService::CreateResetProfile(
1748 nsIToolkitProfile** aNewProfile) {
1749 nsAutoCString oldProfileName;
1750 mCurrent->GetName(oldProfileName);
1752 nsCOMPtr<nsIToolkitProfile> newProfile;
1753 // Make the new profile name the old profile (or "default-") + the time in
1754 // seconds since epoch for uniqueness.
1755 nsAutoCString newProfileName;
1756 if (!oldProfileName.IsEmpty()) {
1757 newProfileName.Assign(oldProfileName);
1758 newProfileName.Append("-");
1759 } else {
1760 newProfileName.AssignLiteral("default-");
1762 newProfileName.AppendPrintf("%" PRId64, PR_Now() / 1000);
1763 nsresult rv = CreateProfile(nullptr, // choose a default dir for us
1764 newProfileName, getter_AddRefs(newProfile));
1765 if (NS_FAILED(rv)) return rv;
1767 mCurrent = newProfile;
1768 newProfile.forget(aNewProfile);
1770 // Don't flush the changes yet. That will happen once the migration
1771 // successfully completes.
1772 return NS_OK;
1776 * This is responsible for deleting the old profile, copying its name to the
1777 * current profile and if the old profile was default making the new profile
1778 * default as well.
1780 nsresult nsToolkitProfileService::ApplyResetProfile(
1781 nsIToolkitProfile* aOldProfile) {
1782 // If the old profile would have been the default for old installs then mark
1783 // the new profile as such.
1784 if (mNormalDefault == aOldProfile) {
1785 SetNormalDefault(mCurrent);
1788 if (mUseDedicatedProfile && mDedicatedProfile == aOldProfile) {
1789 bool wasLocked = false;
1790 nsCString val;
1791 if (NS_SUCCEEDED(
1792 mProfileDB.GetString(mInstallSection.get(), "Locked", val))) {
1793 wasLocked = val.Equals("1");
1796 SetDefaultProfile(mCurrent);
1798 // Make the locked state match if necessary.
1799 if (!wasLocked) {
1800 mProfileDB.DeleteString(mInstallSection.get(), "Locked");
1804 nsCString name;
1805 nsresult rv = aOldProfile->GetName(name);
1806 NS_ENSURE_SUCCESS(rv, rv);
1808 // Don't remove the old profile's files until after we've successfully flushed
1809 // the profile changes to disk.
1810 rv = aOldProfile->Remove(false);
1811 NS_ENSURE_SUCCESS(rv, rv);
1813 // Switching the name will make this the default for dev-edition if
1814 // appropriate.
1815 rv = mCurrent->SetName(name);
1816 NS_ENSURE_SUCCESS(rv, rv);
1818 rv = Flush();
1819 NS_ENSURE_SUCCESS(rv, rv);
1821 // Now that the profile changes are flushed, try to remove the old profile's
1822 // files. If we fail the worst that will happen is that an orphan directory is
1823 // left. Let this run in the background while we start up.
1824 RemoveProfileFiles(aOldProfile, true);
1826 return NS_OK;
1829 NS_IMETHODIMP
1830 nsToolkitProfileService::GetProfileByName(const nsACString& aName,
1831 nsIToolkitProfile** aResult) {
1832 for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1833 if (profile->mName.Equals(aName)) {
1834 NS_ADDREF(*aResult = profile);
1835 return NS_OK;
1839 return NS_ERROR_FAILURE;
1843 * Finds a profile from the database that uses the given root and local
1844 * directories.
1846 void nsToolkitProfileService::GetProfileByDir(nsIFile* aRootDir,
1847 nsIFile* aLocalDir,
1848 nsIToolkitProfile** aResult) {
1849 for (RefPtr<nsToolkitProfile> profile : mProfiles) {
1850 bool equal;
1851 nsresult rv = profile->mRootDir->Equals(aRootDir, &equal);
1852 if (NS_SUCCEEDED(rv) && equal) {
1853 if (!aLocalDir) {
1854 // If no local directory was given then we will just use the normal
1855 // local directory for the profile.
1856 profile.forget(aResult);
1857 return;
1860 rv = profile->mLocalDir->Equals(aLocalDir, &equal);
1861 if (NS_SUCCEEDED(rv) && equal) {
1862 profile.forget(aResult);
1863 return;
1869 nsresult NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath,
1870 nsIProfileUnlocker** aUnlocker,
1871 nsIProfileLock** aResult) {
1872 RefPtr<nsToolkitProfileLock> lock = new nsToolkitProfileLock();
1874 nsresult rv = lock->Init(aPath, aTempPath, aUnlocker);
1875 if (NS_FAILED(rv)) return rv;
1877 lock.forget(aResult);
1878 return NS_OK;
1881 static void SaltProfileName(nsACString& aName) {
1882 char salt[9];
1883 NS_MakeRandomString(salt, 8);
1884 salt[8] = '.';
1886 aName.Insert(salt, 0, 9);
1889 NS_IMETHODIMP
1890 nsToolkitProfileService::CreateUniqueProfile(nsIFile* aRootDir,
1891 const nsACString& aNamePrefix,
1892 nsIToolkitProfile** aResult) {
1893 nsCOMPtr<nsIToolkitProfile> profile;
1894 nsresult rv = GetProfileByName(aNamePrefix, getter_AddRefs(profile));
1895 if (NS_FAILED(rv)) {
1896 return CreateProfile(aRootDir, aNamePrefix, aResult);
1899 uint32_t suffix = 1;
1900 while (true) {
1901 nsPrintfCString name("%s-%d", PromiseFlatCString(aNamePrefix).get(),
1902 suffix);
1903 rv = GetProfileByName(name, getter_AddRefs(profile));
1904 if (NS_FAILED(rv)) {
1905 return CreateProfile(aRootDir, name, aResult);
1907 suffix++;
1911 NS_IMETHODIMP
1912 nsToolkitProfileService::CreateProfile(nsIFile* aRootDir,
1913 const nsACString& aName,
1914 nsIToolkitProfile** aResult) {
1915 nsresult rv = GetProfileByName(aName, aResult);
1916 if (NS_SUCCEEDED(rv)) {
1917 return rv;
1920 nsCOMPtr<nsIFile> rootDir(aRootDir);
1922 nsAutoCString dirName;
1923 if (!rootDir) {
1924 rv = gDirServiceProvider->GetUserProfilesRootDir(getter_AddRefs(rootDir));
1925 NS_ENSURE_SUCCESS(rv, rv);
1927 dirName = aName;
1928 SaltProfileName(dirName);
1930 if (NS_IsNativeUTF8()) {
1931 rootDir->AppendNative(dirName);
1932 } else {
1933 rootDir->Append(NS_ConvertUTF8toUTF16(dirName));
1937 nsCOMPtr<nsIFile> localDir;
1939 bool isRelative;
1940 rv = mAppData->Contains(rootDir, &isRelative);
1941 if (NS_SUCCEEDED(rv) && isRelative) {
1942 nsAutoCString path;
1943 rv = rootDir->GetRelativeDescriptor(mAppData, path);
1944 NS_ENSURE_SUCCESS(rv, rv);
1946 rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localDir));
1947 NS_ENSURE_SUCCESS(rv, rv);
1949 rv = localDir->SetRelativeDescriptor(mTempData, path);
1950 } else {
1951 localDir = rootDir;
1954 bool exists;
1955 rv = rootDir->Exists(&exists);
1956 NS_ENSURE_SUCCESS(rv, rv);
1958 if (exists) {
1959 rv = rootDir->IsDirectory(&exists);
1960 NS_ENSURE_SUCCESS(rv, rv);
1962 if (!exists) return NS_ERROR_FILE_NOT_DIRECTORY;
1963 } else {
1964 nsCOMPtr<nsIFile> profileDirParent;
1965 nsAutoString profileDirName;
1967 rv = rootDir->GetParent(getter_AddRefs(profileDirParent));
1968 NS_ENSURE_SUCCESS(rv, rv);
1970 rv = rootDir->GetLeafName(profileDirName);
1971 NS_ENSURE_SUCCESS(rv, rv);
1973 // let's ensure that the profile directory exists.
1974 rv = rootDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1975 NS_ENSURE_SUCCESS(rv, rv);
1976 rv = rootDir->SetPermissions(0700);
1977 #ifndef ANDROID
1978 // If the profile is on the sdcard, this will fail but its non-fatal
1979 NS_ENSURE_SUCCESS(rv, rv);
1980 #endif
1983 rv = localDir->Exists(&exists);
1984 NS_ENSURE_SUCCESS(rv, rv);
1986 if (!exists) {
1987 rv = localDir->Create(nsIFile::DIRECTORY_TYPE, 0700);
1988 NS_ENSURE_SUCCESS(rv, rv);
1991 // We created a new profile dir. Let's store a creation timestamp.
1992 // Note that this code path does not apply if the profile dir was
1993 // created prior to launching.
1994 rv = CreateTimesInternal(rootDir);
1995 NS_ENSURE_SUCCESS(rv, rv);
1997 nsCOMPtr<nsIToolkitProfile> profile =
1998 new nsToolkitProfile(aName, rootDir, localDir, false);
2000 if (aName.Equals(DEV_EDITION_NAME)) {
2001 mDevEditionDefault = profile;
2004 profile.forget(aResult);
2005 return NS_OK;
2009 * Snaps (https://snapcraft.io/) use a different installation directory for
2010 * every version of an application. Since dedicated profiles uses the
2011 * installation directory to determine which profile to use this would lead
2012 * snap users getting a new profile on every application update.
2014 * However the only way to have multiple installation of a snap is to install
2015 * a new snap instance. Different snap instances have different user data
2016 * directories and so already will not share profiles, in fact one instance
2017 * will not even be able to see the other instance's profiles since
2018 * profiles.ini will be stored in different places.
2020 * So we can just disable dedicated profile support in this case and revert
2021 * back to the old method of just having a single default profile and still
2022 * get essentially the same benefits as dedicated profiles provides.
2024 bool nsToolkitProfileService::IsSnapEnvironment() {
2025 #ifdef MOZ_WIDGET_GTK
2026 return widget::IsRunningUnderSnap();
2027 #else
2028 return false;
2029 #endif
2033 * In some situations dedicated profile support does not work well. This
2034 * includes a handful of linux distributions which always install different
2035 * application versions to different locations, some application sandboxing
2036 * systems as well as enterprise deployments. This environment variable provides
2037 * a way to opt out of dedicated profiles for these cases.
2039 * For Windows, we provide a policy to accomplish the same thing.
2041 bool nsToolkitProfileService::UseLegacyProfiles() {
2042 bool legacyProfiles = !!PR_GetEnv("MOZ_LEGACY_PROFILES");
2043 #ifdef XP_WIN
2044 legacyProfiles |= PolicyCheckBoolean(L"LegacyProfiles");
2045 #endif
2046 return legacyProfiles;
2049 struct FindInstallsClosure {
2050 nsINIParser* installData;
2051 nsTArray<nsCString>* installs;
2054 static bool FindInstalls(const char* aSection, void* aClosure) {
2055 FindInstallsClosure* closure = static_cast<FindInstallsClosure*>(aClosure);
2057 // Check if the section starts with "Install"
2058 if (strncmp(aSection, INSTALL_PREFIX, INSTALL_PREFIX_LENGTH) != 0) {
2059 return true;
2062 nsCString install(aSection);
2063 closure->installs->AppendElement(install);
2065 return true;
2068 nsTArray<nsCString> nsToolkitProfileService::GetKnownInstalls() {
2069 nsTArray<nsCString> result;
2070 FindInstallsClosure closure = {&mProfileDB, &result};
2072 mProfileDB.GetSections(&FindInstalls, &closure);
2074 return result;
2077 nsresult nsToolkitProfileService::CreateTimesInternal(nsIFile* aProfileDir) {
2078 nsresult rv = NS_ERROR_FAILURE;
2079 nsCOMPtr<nsIFile> creationLog;
2080 rv = aProfileDir->Clone(getter_AddRefs(creationLog));
2081 NS_ENSURE_SUCCESS(rv, rv);
2083 rv = creationLog->AppendNative("times.json"_ns);
2084 NS_ENSURE_SUCCESS(rv, rv);
2086 bool exists = false;
2087 creationLog->Exists(&exists);
2088 if (exists) {
2089 return NS_OK;
2092 rv = creationLog->Create(nsIFile::NORMAL_FILE_TYPE, 0700);
2093 NS_ENSURE_SUCCESS(rv, rv);
2095 // We don't care about microsecond resolution.
2096 int64_t msec = PR_Now() / PR_USEC_PER_MSEC;
2098 // Write it out.
2099 PRFileDesc* writeFile;
2100 rv = creationLog->OpenNSPRFileDesc(PR_WRONLY, 0700, &writeFile);
2101 NS_ENSURE_SUCCESS(rv, rv);
2103 PR_fprintf(writeFile, "{\n\"created\": %lld,\n\"firstUse\": null\n}\n", msec);
2104 PR_Close(writeFile);
2105 return NS_OK;
2108 NS_IMETHODIMP
2109 nsToolkitProfileService::GetProfileCount(uint32_t* aResult) {
2110 *aResult = 0;
2111 for (nsToolkitProfile* profile : mProfiles) {
2112 Unused << profile;
2113 (*aResult)++;
2116 return NS_OK;
2119 NS_IMETHODIMP
2120 nsToolkitProfileService::Flush() {
2121 if (GetIsListOutdated()) {
2122 return NS_ERROR_DATABASE_CHANGED;
2125 nsresult rv;
2127 // If we aren't using dedicated profiles then nothing about the list of
2128 // installs can have changed, so no need to update the backup.
2129 if (mUseDedicatedProfile) {
2130 // Export the installs to the backup.
2131 nsTArray<nsCString> installs = GetKnownInstalls();
2133 if (!installs.IsEmpty()) {
2134 nsCString data;
2135 nsCString buffer;
2137 for (uint32_t i = 0; i < installs.Length(); i++) {
2138 nsTArray<UniquePtr<KeyValue>> strings =
2139 GetSectionStrings(&mProfileDB, installs[i].get());
2140 if (strings.IsEmpty()) {
2141 continue;
2144 // Strip "Install" from the start.
2145 const nsDependentCSubstring& install =
2146 Substring(installs[i], INSTALL_PREFIX_LENGTH);
2147 data.AppendPrintf("[%s]\n", PromiseFlatCString(install).get());
2149 for (uint32_t j = 0; j < strings.Length(); j++) {
2150 data.AppendPrintf("%s=%s\n", strings[j]->key.get(),
2151 strings[j]->value.get());
2154 data.Append("\n");
2157 FILE* writeFile;
2158 rv = mInstallDBFile->OpenANSIFileDesc("w", &writeFile);
2159 NS_ENSURE_SUCCESS(rv, rv);
2161 uint32_t length = data.Length();
2162 if (fwrite(data.get(), sizeof(char), length, writeFile) != length) {
2163 fclose(writeFile);
2164 return NS_ERROR_UNEXPECTED;
2167 fclose(writeFile);
2168 } else {
2169 rv = mInstallDBFile->Remove(false);
2170 if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
2171 return rv;
2176 rv = mProfileDB.WriteToFile(mProfileDBFile);
2177 NS_ENSURE_SUCCESS(rv, rv);
2179 rv = UpdateFileStats(mProfileDBFile, &mProfileDBExists,
2180 &mProfileDBModifiedTime, &mProfileDBFileSize);
2181 NS_ENSURE_SUCCESS(rv, rv);
2183 return NS_OK;
2186 already_AddRefed<nsToolkitProfileService> NS_GetToolkitProfileService() {
2187 if (!nsToolkitProfileService::gService) {
2188 nsToolkitProfileService::gService = new nsToolkitProfileService();
2189 nsresult rv = nsToolkitProfileService::gService->Init();
2190 if (NS_FAILED(rv)) {
2191 NS_ERROR("nsToolkitProfileService::Init failed!");
2192 delete nsToolkitProfileService::gService;
2193 return nullptr;
2197 return do_AddRef(nsToolkitProfileService::gService);
2200 nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult) {
2201 #if defined(XP_MACOSX)
2202 int32_t pathLen = strlen(aPath);
2203 if (pathLen > MAXPATHLEN) return NS_ERROR_INVALID_ARG;
2205 CFURLRef fullPath = CFURLCreateFromFileSystemRepresentation(
2206 nullptr, (const UInt8*)aPath, pathLen, true);
2207 if (!fullPath) return NS_ERROR_FAILURE;
2209 nsCOMPtr<nsIFile> lf;
2210 nsresult rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(lf));
2211 if (NS_SUCCEEDED(rv)) {
2212 nsCOMPtr<nsILocalFileMac> lfMac = do_QueryInterface(lf, &rv);
2213 if (NS_SUCCEEDED(rv)) {
2214 rv = lfMac->InitWithCFURL(fullPath);
2215 if (NS_SUCCEEDED(rv)) {
2216 lf.forget(aResult);
2220 CFRelease(fullPath);
2221 return rv;
2223 #elif defined(XP_UNIX)
2224 char fullPath[MAXPATHLEN];
2226 if (!realpath(aPath, fullPath)) return NS_ERROR_FAILURE;
2228 return NS_NewNativeLocalFile(nsDependentCString(fullPath), true, aResult);
2229 #elif defined(XP_WIN)
2230 WCHAR fullPath[MAXPATHLEN];
2232 if (!_wfullpath(fullPath, NS_ConvertUTF8toUTF16(aPath).get(), MAXPATHLEN))
2233 return NS_ERROR_FAILURE;
2235 return NS_NewLocalFile(nsDependentString(fullPath), true, aResult);
2237 #else
2238 # error Platform-specific logic needed here.
2239 #endif