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