From 2215cf5e3fbcfdbec633d3bb9f457e89d71dba11 Mon Sep 17 00:00:00 2001
From: Kathy Brade <brade@pearlcrescent.com>
Date: Tue, 24 Feb 2015 13:50:23 -0500
Subject: [PATCH] Bug 14631: Improve profile access error messages.

Instead of always reporting that the profile is locked, display specific
messages for "access denied" and "read-only file system".
---
 .../profile/profileSelection.properties       |  5 ++
 toolkit/profile/nsToolkitProfileService.cpp   | 57 ++++++++++++++++-
 toolkit/profile/nsToolkitProfileService.h     | 13 +++-
 toolkit/xre/nsAppRunner.cpp                   | 64 +++++++++++++------
 4 files changed, 118 insertions(+), 21 deletions(-)

diff --git a/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties b/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties
index 242c8c78b251d..cf145367f1be1 100644
--- a/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties
+++ b/toolkit/locales/en-US/chrome/mozapps/profile/profileSelection.properties
@@ -12,6 +12,11 @@ restartMessageUnlocker=%S is already running, but is not responding. The old %S
 restartMessageNoUnlockerMac=A copy of %S is already open. Only one copy of %S can be open at a time.
 restartMessageUnlockerMac=A copy of %S is already open. The running copy of %S will quit in order to open this one.
 
+# LOCALIZATION NOTE (profileProblemTitle, profileReadOnly, profileReadOnlyMac, profileAccessDenied):  Messages displayed when the browser profile cannot be accessed or written to. %S is the application name.
+profileProblemTitle=%S Profile Problem
+profileReadOnly=You cannot run %S from a read-only file system.  Please copy %S to another location before trying to use it.
+profileReadOnlyMac=You cannot run %S from a read-only file system.  Please copy %S to your Desktop or Applications folder before trying to use it.
+profileAccessDenied=%S does not have permission to access the profile. Please adjust your file system permissions and try again.
 # Profile manager
 # LOCALIZATION NOTE (profileTooltip): First %S is the profile name, second %S is the path to the profile folder.
 profileTooltip=Profile: ‘%S’ - Path: ‘%S’
diff --git a/toolkit/profile/nsToolkitProfileService.cpp b/toolkit/profile/nsToolkitProfileService.cpp
index 7c23ffec9ccf9..88dfbafcdc3d0 100644
--- a/toolkit/profile/nsToolkitProfileService.cpp
+++ b/toolkit/profile/nsToolkitProfileService.cpp
@@ -1136,9 +1136,10 @@ nsToolkitProfileService::SelectStartupProfile(
   }
 
   bool wasDefault;
+  ProfileStatus profileStatus;
   nsresult rv =
       SelectStartupProfile(&argc, argv.get(), aIsResetting, aRootDir, aLocalDir,
-                           aProfile, aDidCreate, &wasDefault);
+                           aProfile, aDidCreate, &wasDefault, profileStatus);
 
   // Since we were called outside of the normal startup path complete any
   // startup tasks.
@@ -1171,7 +1172,8 @@ nsToolkitProfileService::SelectStartupProfile(
 nsresult nsToolkitProfileService::SelectStartupProfile(
     int* aArgc, char* aArgv[], bool aIsResetting, nsIFile** aRootDir,
     nsIFile** aLocalDir, nsIToolkitProfile** aProfile, bool* aDidCreate,
-    bool* aWasDefaultSelection) {
+    bool* aWasDefaultSelection, ProfileStatus& aProfileStatus) {
+  aProfileStatus = PROFILE_STATUS_OK;
   if (mStartupProfileSelected) {
     return NS_ERROR_ALREADY_INITIALIZED;
   }
@@ -1265,6 +1267,13 @@ nsresult nsToolkitProfileService::SelectStartupProfile(
     rv = XRE_GetFileFromPath(arg, getter_AddRefs(lf));
     NS_ENSURE_SUCCESS(rv, rv);
 
+    aProfileStatus = CheckProfileWriteAccess(lf);
+    if (PROFILE_STATUS_OK != aProfileStatus) {
+      NS_ADDREF(*aRootDir = lf);
+      NS_ADDREF(*aLocalDir = lf);
+      return NS_ERROR_FAILURE;
+    }
+
     // Make sure that the profile path exists and it's a directory.
     bool exists;
     rv = lf->Exists(&exists);
@@ -2047,3 +2056,47 @@ nsresult XRE_GetFileFromPath(const char* aPath, nsIFile** aResult) {
 #  error Platform-specific logic needed here.
 #endif
 }
+
+// Check for write permission to the profile directory by trying to create a
+// new file (after ensuring that no file with the same name exists).
+ProfileStatus nsToolkitProfileService::CheckProfileWriteAccess(
+    nsIFile* aProfileDir) {
+#if defined(XP_UNIX)
+  NS_NAMED_LITERAL_STRING(writeTestFileName, ".parentwritetest");
+#else
+  NS_NAMED_LITERAL_STRING(writeTestFileName, "parent.writetest");
+#endif
+
+  nsCOMPtr<nsIFile> writeTestFile;
+  nsresult rv = aProfileDir->Clone(getter_AddRefs(writeTestFile));
+  if (NS_SUCCEEDED(rv)) rv = writeTestFile->Append(writeTestFileName);
+
+  if (NS_SUCCEEDED(rv)) {
+    bool doesExist = false;
+    rv = writeTestFile->Exists(&doesExist);
+    if (NS_SUCCEEDED(rv) && doesExist) rv = writeTestFile->Remove(true);
+  }
+
+  if (NS_SUCCEEDED(rv)) {
+    rv = writeTestFile->Create(nsIFile::NORMAL_FILE_TYPE, 0666);
+    (void)writeTestFile->Remove(true);
+  }
+
+  ProfileStatus status =
+      NS_SUCCEEDED(rv) ? PROFILE_STATUS_OK : PROFILE_STATUS_OTHER_ERROR;
+  if (NS_ERROR_FILE_ACCESS_DENIED == rv)
+    status = PROFILE_STATUS_ACCESS_DENIED;
+  else if (NS_ERROR_FILE_READ_ONLY == rv)
+    status = PROFILE_STATUS_READ_ONLY;
+
+  return status;
+}
+
+ProfileStatus nsToolkitProfileService::CheckProfileWriteAccess(
+    nsIToolkitProfile* aProfile) {
+  nsCOMPtr<nsIFile> profileDir;
+  nsresult rv = aProfile->GetRootDir(getter_AddRefs(profileDir));
+  if (NS_FAILED(rv)) return PROFILE_STATUS_OTHER_ERROR;
+
+  return CheckProfileWriteAccess(profileDir);
+}
diff --git a/toolkit/profile/nsToolkitProfileService.h b/toolkit/profile/nsToolkitProfileService.h
index 73292e9d4348b..4901dd32b7e28 100644
--- a/toolkit/profile/nsToolkitProfileService.h
+++ b/toolkit/profile/nsToolkitProfileService.h
@@ -15,6 +15,14 @@
 #include "nsProfileLock.h"
 #include "nsINIParser.h"
 
+enum ProfileStatus {
+  PROFILE_STATUS_OK,
+  PROFILE_STATUS_ACCESS_DENIED,
+  PROFILE_STATUS_READ_ONLY,
+  PROFILE_STATUS_IS_LOCKED,
+  PROFILE_STATUS_OTHER_ERROR
+};
+
 class nsToolkitProfile final
     : public nsIToolkitProfile,
       public mozilla::LinkedListElement<RefPtr<nsToolkitProfile>> {
@@ -79,10 +87,13 @@ class nsToolkitProfileService final : public nsIToolkitProfileService {
   nsresult SelectStartupProfile(int* aArgc, char* aArgv[], bool aIsResetting,
                                 nsIFile** aRootDir, nsIFile** aLocalDir,
                                 nsIToolkitProfile** aProfile, bool* aDidCreate,
-                                bool* aWasDefaultSelection);
+                                bool* aWasDefaultSelection,
+                                ProfileStatus& aProfileStatus);
   nsresult CreateResetProfile(nsIToolkitProfile** aNewProfile);
   nsresult ApplyResetProfile(nsIToolkitProfile* aOldProfile);
   void CompleteStartup();
+  static ProfileStatus CheckProfileWriteAccess(nsIToolkitProfile* aProfile);
+  static ProfileStatus CheckProfileWriteAccess(nsIFile* aProfileDir);
 
  private:
   friend class nsToolkitProfile;
diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp
index d3d2d66d5de41..c06b9ad7eff9a 100644
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -1713,11 +1713,12 @@ static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) {
   }
 }
 
-static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir,
-                                              nsIFile* aProfileLocalDir,
-                                              nsIProfileUnlocker* aUnlocker,
-                                              nsINativeAppSupport* aNative,
-                                              nsIProfileLock** aResult) {
+static ReturnAbortOnError ProfileErrorDialog(nsIFile* aProfileDir,
+                                             nsIFile* aProfileLocalDir,
+                                             ProfileStatus aStatus,
+                                             nsIProfileUnlocker* aUnlocker,
+                                             nsINativeAppSupport* aNative,
+                                             nsIProfileLock** aResult) {
   nsresult rv;
 
   bool exists;
@@ -1750,18 +1751,31 @@ static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir,
 
     nsAutoString killMessage;
 #ifndef XP_MACOSX
-    rv = sb->FormatStringFromName(
-        aUnlocker ? "restartMessageUnlocker" : "restartMessageNoUnlocker",
-        params, 2, killMessage);
+    static const char* kRestartUnlocker = "restartMessageUnlocker";
+    static const char* kRestartNoUnlocker = "restartMessageNoUnlocker";
+    static const char* kReadOnly = "profileReadOnly";
 #else
-    rv = sb->FormatStringFromName(
-        aUnlocker ? "restartMessageUnlockerMac" : "restartMessageNoUnlockerMac",
-        params, 2, killMessage);
+    static const char* kRestartUnlocker = "restartMessageUnlockerMac";
+    static const char* kRestartNoUnlocker = "restartMessageNoUnlockerMac";
+    static const char* kReadOnly = "profileReadOnlyMac";
 #endif
+    static const char* kAccessDenied = "profileAccessDenied";
+
+    const char* errorKey = aUnlocker ? kRestartUnlocker : kRestartNoUnlocker;
+    if (PROFILE_STATUS_READ_ONLY == aStatus)
+      errorKey = kReadOnly;
+    else if (PROFILE_STATUS_ACCESS_DENIED == aStatus)
+      errorKey = kAccessDenied;
+    rv = sb->FormatStringFromName(errorKey, params, 2, killMessage);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
+    const char* titleKey = ((PROFILE_STATUS_READ_ONLY == aStatus) ||
+                            (PROFILE_STATUS_ACCESS_DENIED == aStatus))
+                               ? "profileProblemTitle"
+                               : "restartTitle";
+
     nsAutoString killTitle;
-    rv = sb->FormatStringFromName("restartTitle", params, 1, killTitle);
+    rv = sb->FormatStringFromName(titleKey, params, 1, killTitle);
     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
 
     if (gfxPlatform::IsHeadless()) {
@@ -1923,6 +1937,13 @@ static nsCOMPtr<nsIToolkitProfile> gResetOldProfile;
 static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir,
                             nsIFile* aLocalDir, nsIToolkitProfile* aProfile,
                             nsIProfileLock** aResult) {
+  ProfileStatus status =
+      (aProfile ? nsToolkitProfileService::CheckProfileWriteAccess(aProfile)
+                : nsToolkitProfileService::CheckProfileWriteAccess(aRootDir));
+  if (PROFILE_STATUS_OK != status)
+    return ProfileErrorDialog(aRootDir, aLocalDir, status, nullptr, aNative,
+                              aResult);
+
   // If you close Firefox and very quickly reopen it, the old Firefox may
   // still be closing down. Rather than immediately showing the
   // "Firefox is running but is not responding" message, we spend a few
@@ -1949,7 +1970,8 @@ static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir,
   } while (TimeStamp::Now() - start <
            TimeDuration::FromSeconds(kLockRetrySeconds));
 
-  return ProfileLockedDialog(aRootDir, aLocalDir, unlocker, aNative, aResult);
+  return ProfileErrorDialog(aRootDir, aLocalDir, PROFILE_STATUS_IS_LOCKED,
+                            unlocker, aNative, aResult);
 }
 
 // Pick a profile. We need to end up with a profile root dir, local dir and
@@ -1964,7 +1986,8 @@ static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir,
 static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc,
                               nsINativeAppSupport* aNative, nsIFile** aRootDir,
                               nsIFile** aLocalDir, nsIToolkitProfile** aProfile,
-                              bool* aWasDefaultSelection) {
+                              bool* aWasDefaultSelection,
+                              nsIProfileLock** aResult) {
   StartupTimeline::Record(StartupTimeline::SELECT_PROFILE);
 
   nsresult rv;
@@ -2000,9 +2023,14 @@ static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc,
 
   // Ask the profile manager to select the profile directories to use.
   bool didCreate = false;
-  rv = aProfileSvc->SelectStartupProfile(&gArgc, gArgv, gDoProfileReset,
-                                         aRootDir, aLocalDir, aProfile,
-                                         &didCreate, aWasDefaultSelection);
+  ProfileStatus profileStatus = PROFILE_STATUS_OK;
+  rv = aProfileSvc->SelectStartupProfile(
+      &gArgc, gArgv, gDoProfileReset, aRootDir, aLocalDir, aProfile, &didCreate,
+      aWasDefaultSelection, profileStatus);
+  if (PROFILE_STATUS_OK != profileStatus) {
+    return ProfileErrorDialog(*aRootDir, *aLocalDir, profileStatus, nullptr,
+                              aNative, aResult);
+  }
 
   if (rv == NS_ERROR_SHOW_PROFILE_MANAGER) {
     return ShowProfileManager(aProfileSvc, aNative);
@@ -3946,7 +3974,7 @@ int XREMain::XRE_mainStartup(bool* aExitFlag) {
   nsCOMPtr<nsIToolkitProfile> profile;
   rv = SelectProfile(mProfileSvc, mNativeApp, getter_AddRefs(mProfD),
                      getter_AddRefs(mProfLD), getter_AddRefs(profile),
-                     &wasDefaultSelection);
+                     &wasDefaultSelection, getter_AddRefs(mProfileLock));
   if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) {
     *aExitFlag = true;
     return 0;
-- 
GitLab