/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- * * The contents of this file are subject to the Netscape Public License * Version 1.0 (the "NPL"); you may not use this file except in * compliance with the NPL. You may obtain a copy of the NPL at * http://www.mozilla.org/NPL/ * * Software distributed under the NPL is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL * for the specific language governing rights and limitations under the * NPL. * * The Initial Developer of this code under the NPL is Netscape * Communications Corporation. Portions created by Netscape are * Copyright (C) 1998 Netscape Communications Corporation. All Rights * Reserved. * * Contributor(s): * Pierre Phaneuf */ #include "nsProfileAccess.h" #include "nsProfile.h" #include "pratom.h" #include "prmem.h" #include "plstr.h" #include "prenv.h" #include "nsIEnumerator.h" #include "prprf.h" #include "nsSpecialSystemDirectory.h" #include "nsCOMPtr.h" #include "nsIComponentManager.h" #include "nsFileStream.h" #include "nsEscape.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" #include "nsILocalFile.h" #include "nsReadableUtils.h" #if defined(XP_MAC) || defined(XP_MACOSX) #include #include #endif #ifdef XP_UNIX #include #include #include #include #include "prnetdb.h" #include "prsystem.h" #endif #ifdef VMS #include #endif #include "nsICharsetConverterManager.h" #include "nsIPlatformCharset.h" #if defined (XP_UNIX) #define USER_ENVIRONMENT_VARIABLE "USER" #define LOGNAME_ENVIRONMENT_VARIABLE "LOGNAME" #define HOME_ENVIRONMENT_VARIABLE "HOME" #define PROFILE_NAME_ENVIRONMENT_VARIABLE "PROFILE_NAME" #define PROFILE_HOME_ENVIRONMENT_VARIABLE "PROFILE_HOME" #define DEFAULT_UNIX_PROFILE_NAME "default" #ifndef XP_MACOSX /* Don't use symlink-based locking on OS X */ #define USE_SYMLINK_LOCKING #endif #elif defined (XP_BEOS) #endif // IID and CIDs of all the services needed static NS_DEFINE_CID(kRegistryCID, NS_REGISTRY_CID); static NS_DEFINE_CID(kCharsetConverterManagerCID, NS_ICHARSETCONVERTERMANAGER_CID); // Registry Keys #define kRegistryYesString (NS_LITERAL_STRING("yes")) #define kRegistryNoString (NS_LITERAL_STRING("no")) #define kRegistryProfileSubtreeString (NS_LITERAL_STRING("Profiles")) #define kRegistryCurrentProfileString (NS_LITERAL_STRING("CurrentProfile")) #define kRegistryNCServiceDenialString (NS_LITERAL_STRING("NCServiceDenial")) #define kRegistryNCProfileNameString (NS_LITERAL_STRING("NCProfileName")) #define kRegistryNCUserEmailString (NS_LITERAL_STRING("NCEmailAddress")) #define kRegistryNCHavePREGInfoString (NS_LITERAL_STRING("NCHavePregInfo")) #define kRegistryHavePREGInfoString (NS_LITERAL_STRING("HavePregInfo")) #define kRegistryMigratedString (NS_LITERAL_STRING("migrated")) #define kRegistryDirectoryString (NS_LITERAL_STRING("directory")) #define kRegistryNeedMigrationString (NS_LITERAL_STRING("NeedMigration")) #define kRegistryMozRegDataMovedString (NS_LITERAL_STRING("OldRegDataMoved")) #define kRegistryCreationTimeString (NS_LITERAL_CSTRING("CreationTime")) #define kRegistryLastModTimeString (NS_LITERAL_CSTRING("LastModTime")) #define kRegistryMigratedFromString (NS_LITERAL_CSTRING("MigFromDir")) #define kRegistryVersionString (NS_LITERAL_STRING("Version")) #define kRegistryVersion_1_0 (NS_LITERAL_STRING("1.0")) #define kRegistryCurrentVersion (NS_LITERAL_STRING("1.0")) // ********************************************************************** // class nsProfileAccess // ********************************************************************** /* * Constructor/Destructor * FillProfileInfo reads the registry and fills profileStructs */ nsProfileAccess::nsProfileAccess() { mProfileDataChanged = PR_FALSE; mForgetProfileCalled = PR_FALSE; m4xProfilesAdded = PR_FALSE; mProfiles = new nsVoidArray(); // Get the profile registry path NS_GetSpecialDirectory(NS_APP_APPLICATION_REGISTRY_FILE, getter_AddRefs(mNewRegFile)); // Read the data into internal data structure.... FillProfileInfo(mNewRegFile); } // On the way out, close the registry if it is // still opened and free up the resources. nsProfileAccess::~nsProfileAccess() { // Release all resources. mNewRegFile = nsnull; FreeProfileMembers(mProfiles); } // A wrapper function to call the interface to get a platform file charset. nsresult GetPlatformCharset(nsString& aCharset) { nsresult rv; // we may cache it since the platform charset will not change through application life nsCOMPtr platformCharset = do_GetService(NS_PLATFORMCHARSET_CONTRACTID, &rv); if (NS_SUCCEEDED(rv) && platformCharset) { rv = platformCharset->GetCharset(kPlatformCharsetSel_FileName, aCharset); } if (NS_FAILED(rv)) { aCharset.Assign(NS_LITERAL_STRING("ISO-8859-1")); // use ISO-8859-1 in case of any error } return rv; } // Apply a charset conversion from the given charset to Unicode for input C string. nsresult ConvertStringToUnicode(nsString& aCharset, const char* inString, nsAString& outString) { nsresult rv; // convert result to unicode nsCOMPtr ccm = do_GetService(kCharsetConverterManagerCID, &rv); if(NS_SUCCEEDED(rv)) { nsCOMPtr decoder; // this may be cached rv = ccm->GetUnicodeDecoder(&aCharset, getter_AddRefs(decoder)); if(NS_SUCCEEDED(rv) && decoder) { PRInt32 uniLength = 0; PRInt32 srcLength = strlen(inString); rv = decoder->GetMaxLength(inString, srcLength, &uniLength); if (NS_SUCCEEDED(rv)) { PRUnichar *unichars = new PRUnichar [uniLength]; if (nsnull != unichars) { // convert to unicode rv = decoder->Convert(inString, &srcLength, unichars, &uniLength); if (NS_SUCCEEDED(rv)) { // Pass back the unicode string outString.Assign(unichars, uniLength); } delete [] unichars; } else { rv = NS_ERROR_OUT_OF_MEMORY; } } } } return rv; } // Free up the member profile structs void nsProfileAccess::FreeProfileMembers(nsVoidArray *profiles) { NS_ASSERTION(profiles, "Invalid profiles"); PRInt32 index = 0; PRInt32 numElems = profiles->Count(); ProfileStruct* aProfile; if (profiles) { for (index = 0; index < numElems; index++) { aProfile = (ProfileStruct *) profiles->ElementAt(index); delete aProfile; } delete profiles; } } // Given the name of the profile, the structure that // contains the relavant profile information will be filled. // Caller must free up the profile struct. nsresult nsProfileAccess::GetValue(const PRUnichar* profileName, ProfileStruct** aProfile) { NS_ENSURE_ARG(profileName); NS_ENSURE_ARG_POINTER(aProfile); *aProfile = nsnull; PRInt32 index = 0; index = FindProfileIndex(profileName, PR_FALSE); if (index < 0) return NS_ERROR_FAILURE; ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); *aProfile = new ProfileStruct(*profileItem); if (!*aProfile) return NS_ERROR_OUT_OF_MEMORY; return NS_OK; } // This method writes all changes to the array of the // profile structs. If it is an existing profile, it // will be updated. If it is a new profile, it gets added // to the list. nsresult nsProfileAccess::SetValue(ProfileStruct* aProfile) { NS_ENSURE_ARG(aProfile); PRInt32 index = 0; ProfileStruct* profileItem; index = FindProfileIndex(aProfile->profileName.get(), aProfile->isImportType); if (index >= 0) { profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); *profileItem = *aProfile; profileItem->updateProfileEntry = PR_TRUE; } else { profileItem = new ProfileStruct(*aProfile); if (!profileItem) return NS_ERROR_OUT_OF_MEMORY; profileItem->updateProfileEntry = PR_TRUE; if (!mProfiles) { mProfiles = new nsVoidArray; if (!mProfiles) return NS_ERROR_OUT_OF_MEMORY; } mProfiles->AppendElement((void*)profileItem); } return NS_OK; } // Enumerates through the registry for profile // information. Reads in the data into the array // of profile structs. After this, all the callers // requesting profile info will get thier data from // profiles array. All the udates will be done to this // data structure to reflect the latest status. // Data will be flushed at the end. nsresult nsProfileAccess::FillProfileInfo(nsIFile* regName) { nsresult rv = NS_OK; nsCOMPtr registry(do_CreateInstance(NS_REGISTRY_CONTRACTID, &rv)); if (NS_FAILED(rv)) return rv; rv = registry->Open(regName); if (NS_FAILED(rv)) return rv; // Enumerate all subkeys (immediately) under the given node. nsCOMPtr enumKeys; nsRegistryKey profilesTreeKey; rv = registry->GetKey(nsIRegistry::Common, kRegistryProfileSubtreeString.get(), &profilesTreeKey); if (NS_FAILED(rv)) { rv = registry->AddKey(nsIRegistry::Common, kRegistryProfileSubtreeString.get(), &profilesTreeKey); if (NS_FAILED(rv)) return rv; } // introducing these tmp variables as nsString variables cannot be passed to // the resgitry methods nsXPIDLString tmpCurrentProfile; nsXPIDLString tmpVersion; nsXPIDLString tmpPREGInfo; // For the following variables, we do not check for the rv value // but check for the variable instead, because it is valid to proceed // without the variables having a value. That's why there are no returns // for invalid rv values. // Get the current profile rv = registry->GetString(profilesTreeKey, kRegistryCurrentProfileString.get(), getter_Copies(tmpCurrentProfile)); if (tmpCurrentProfile) { // If current profile does not exist, mCurrentProfile will not be set // This is not harmful, as GetCurrentProfile method needs to return this value // And GetCurrentProfile returns: // the current profile if set // the first profile if profiles exist but no current profile is set // an empty string if no profiles exist. mCurrentProfile = NS_STATIC_CAST(const PRUnichar*, tmpCurrentProfile); } // Get the profile version rv = registry->GetString(profilesTreeKey, kRegistryVersionString.get(), getter_Copies(tmpVersion)); // Get the preg info rv = registry->GetString(profilesTreeKey, kRegistryHavePREGInfoString.get(), getter_Copies(tmpPREGInfo)); if (tmpPREGInfo == nsnull) { mHavePREGInfo = kRegistryNoString; mProfileDataChanged = PR_TRUE; } rv = registry->EnumerateSubtrees( profilesTreeKey, getter_AddRefs(enumKeys)); if (NS_FAILED(rv)) return rv; rv = enumKeys->First(); if (NS_FAILED(rv)) return rv; PRBool currentProfileValid = mCurrentProfile.IsEmpty(); while (NS_OK != enumKeys->IsDone()) { nsCOMPtr base; rv = enumKeys->CurrentItem( getter_AddRefs(base) ); if (NS_FAILED(rv)) return rv; // Get specific interface. nsCOMPtr node; nsIID nodeIID = NS_IREGISTRYNODE_IID; rv = base->QueryInterface( nodeIID, getter_AddRefs(node)); if (NS_FAILED(rv)) return rv; // Get node name. nsXPIDLString profile; nsXPIDLString isMigrated; nsXPIDLString NCProfileName; nsXPIDLString NCDeniedService; nsXPIDLString NCEmailAddress; nsXPIDLString NCHavePregInfo; rv = node->GetName(getter_Copies(profile)); if (NS_FAILED(rv)) return rv; nsRegistryKey profKey; rv = node->GetKey(&profKey); if (NS_FAILED(rv)) return rv; rv = registry->GetString(profKey, kRegistryMigratedString.get(), getter_Copies(isMigrated)); if (NS_FAILED(rv)) return rv; nsDependentString isMigratedString(isMigrated); // Not checking the return values of these variables as they // are for activation, they are optional and their values // do not call for a return registry->GetString(profKey, kRegistryNCProfileNameString.get(), getter_Copies(NCProfileName)); registry->GetString(profKey, kRegistryNCServiceDenialString.get(), getter_Copies(NCDeniedService)); registry->GetString(profKey, kRegistryNCUserEmailString.get(), getter_Copies(NCEmailAddress)); registry->GetString(profKey, kRegistryNCHavePREGInfoString.get(), getter_Copies(NCHavePregInfo)); // Make sure that mCurrentProfile is valid if (!mCurrentProfile.IsEmpty() && mCurrentProfile.Equals(profile)) currentProfileValid = PR_TRUE; ProfileStruct* profileItem = new ProfileStruct(); if (!profileItem) return NS_ERROR_OUT_OF_MEMORY; profileItem->updateProfileEntry = PR_TRUE; profileItem->profileName = NS_STATIC_CAST(const PRUnichar*, profile); PRInt64 tmpLongLong; rv = registry->GetLongLong(profKey, kRegistryCreationTimeString.get(), &tmpLongLong); if (NS_SUCCEEDED(rv)) profileItem->creationTime = tmpLongLong; rv = registry->GetLongLong(profKey, kRegistryLastModTimeString.get(), &tmpLongLong); if (NS_SUCCEEDED(rv)) profileItem->lastModTime = tmpLongLong; rv = profileItem->InternalizeLocation(registry, profKey, PR_FALSE); NS_ASSERTION(NS_SUCCEEDED(rv), "Internalizing profile location failed"); // Not checking the error since most won't have this info profileItem->InternalizeMigratedFromLocation(registry, profKey); profileItem->isMigrated = isMigratedString.Equals(kRegistryYesString); if (NCProfileName) profileItem->NCProfileName = NS_STATIC_CAST(const PRUnichar*, NCProfileName); if (NCDeniedService) profileItem->NCDeniedService = NS_STATIC_CAST(const PRUnichar*, NCDeniedService); if (NCEmailAddress) profileItem->NCEmailAddress = NS_STATIC_CAST(const PRUnichar*, NCEmailAddress); if (NCHavePregInfo) profileItem->NCHavePregInfo = NS_STATIC_CAST(const PRUnichar*, NCHavePregInfo); profileItem->isImportType = PR_FALSE; if (!mProfiles) { mProfiles = new nsVoidArray(); if (!mProfiles) { delete profileItem; return NS_ERROR_OUT_OF_MEMORY; } } mProfiles->AppendElement((void*)profileItem); rv = enumKeys->Next(); if (NS_FAILED(rv)) return rv; } if (!currentProfileValid) mCurrentProfile.SetLength(0); return rv; } // Return the number of 5x profiles. void nsProfileAccess::GetNumProfiles(PRInt32 *numProfiles) { if (!numProfiles) { NS_ASSERTION(PR_FALSE, "invalid argument"); return; } PRInt32 index, numElems = mProfiles->Count(); *numProfiles = 0; for(index = 0; index < numElems; index++) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); if (profileItem->isMigrated && !profileItem->isImportType) { (*numProfiles)++; } } } // Return the number of 4x (>=4.5 & < 5.0) profiles. void nsProfileAccess::GetNum4xProfiles(PRInt32 *numProfiles) { if (!numProfiles) { NS_ASSERTION(PR_FALSE, "invalid argument"); return; } PRInt32 index, numElems = mProfiles->Count(); *numProfiles = 0; for(index = 0; index < numElems; index++) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); if (!profileItem->isMigrated && !profileItem->isImportType) { (*numProfiles)++; } } } // If the application can't find the current profile, // the first profile will be used as the current profile. // This routine returns the first 5x profile. // Caller must free up the string (firstProfile). void nsProfileAccess::GetFirstProfile(PRUnichar **firstProfile) { if (!firstProfile) { NS_ASSERTION(PR_FALSE, "Invalid firstProfile pointer"); return; } PRInt32 index, numElems = mProfiles->Count(); *firstProfile = nsnull; for(index = 0; index < numElems; index++) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); if (profileItem->isMigrated && !profileItem->isImportType) { *firstProfile = ToNewUnicode(profileItem->profileName); break; } } } // Set the current profile. Opearting directly on the tree. // A separate struct should be maintained for the top level info. // That way we can eliminate additional registry access. For // now, we depend on registry operations. // Capture the current profile information into mCurrentProfile. void nsProfileAccess::SetCurrentProfile(const PRUnichar *profileName) { NS_ASSERTION(profileName, "Invalid profile name"); mCurrentProfile = profileName; mProfileDataChanged = PR_TRUE; } // Return the current profile value. // If mCurrent profile is already set, that value is returned. // If there is only one profile that value is set to CurrentProfile. void nsProfileAccess::GetCurrentProfile(PRUnichar **profileName) { *profileName = nsnull; if (!mCurrentProfile.IsEmpty() || mForgetProfileCalled) { *profileName = ToNewUnicode(mCurrentProfile); } // If there are profiles and profileName is not // set yet. Get the first one and set it as Current Profile. if (*profileName == nsnull) { GetFirstProfile(profileName); // We might not have any if (*profileName) SetCurrentProfile(*profileName); } } // Delete a profile from profile structs void nsProfileAccess::RemoveSubTree(const PRUnichar* profileName) { NS_ASSERTION(profileName, "Invalid profile name"); // delete this entry from the mProfiles array PRInt32 index = FindProfileIndex(profileName, PR_FALSE); if (index >= 0) { mProfiles->RemoveElementAt(index); if (mCurrentProfile.Equals(profileName)) { mCurrentProfile.SetLength(0); } } } // Return the index of a given profiel from the arraf of profile structs. PRInt32 nsProfileAccess::FindProfileIndex(const PRUnichar* profileName, PRBool forImport) { NS_ASSERTION(profileName, "Invalid profile name"); PRInt32 retval = -1; PRInt32 index, numElems = mProfiles->Count(); for (index=0; index < numElems; index++) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); if(profileItem->profileName.Equals(profileName) && (profileItem->isImportType == forImport)) { retval = index; break; } } return retval; } // Flush profile information from the data structure to the registry. nsresult nsProfileAccess::UpdateRegistry(nsIFile* regName) { nsresult rv; if (!mProfileDataChanged) { return NS_OK; } if (!regName) { regName = mNewRegFile; } nsCOMPtr registry(do_CreateInstance(NS_REGISTRY_CONTRACTID, &rv)); if (NS_FAILED(rv)) return rv; rv = registry->Open(regName); if (NS_FAILED(rv)) return rv; // Enumerate all subkeys (immediately) under the given node. nsCOMPtr enumKeys; nsRegistryKey profilesTreeKey; // Get the major subtree rv = registry->GetKey(nsIRegistry::Common, kRegistryProfileSubtreeString.get(), &profilesTreeKey); if (NS_FAILED(rv)) { rv = registry->AddKey(nsIRegistry::Common, kRegistryProfileSubtreeString.get(), &profilesTreeKey); if (NS_FAILED(rv)) return rv; } // Set the current profile if (!mCurrentProfile.IsEmpty()) { rv = registry->SetString(profilesTreeKey, kRegistryCurrentProfileString.get(), mCurrentProfile.get()); if (NS_FAILED(rv)) return rv; } // Set the registry version rv = registry->SetString(profilesTreeKey, kRegistryVersionString.get(), kRegistryCurrentVersion.get()); if (NS_FAILED(rv)) return rv; // Set preg info rv = registry->SetString(profilesTreeKey, kRegistryHavePREGInfoString.get(), mHavePREGInfo.get()); if (NS_FAILED(rv)) return rv; rv = registry->EnumerateSubtrees(profilesTreeKey, getter_AddRefs(enumKeys)); if (NS_FAILED(rv)) return rv; rv = enumKeys->First(); if (NS_FAILED(rv)) return rv; while (NS_OK != enumKeys->IsDone()) { nsCOMPtr base; rv = enumKeys->CurrentItem( getter_AddRefs(base) ); if (NS_FAILED(rv)) return rv; // Get specific interface. nsCOMPtr node; nsIID nodeIID = NS_IREGISTRYNODE_IID; rv = base->QueryInterface( nodeIID, getter_AddRefs(node)); if (NS_FAILED(rv)) return rv; // Get node name. nsXPIDLString profile; nsXPIDLString isMigrated; nsXPIDLString directory; rv = node->GetName( getter_Copies(profile) ); if (NS_FAILED(rv)) return rv; PRInt32 index = 0; index = FindProfileIndex(profile, PR_FALSE); if (index < 0) { // This profile is deleted. rv = registry->RemoveKey(profilesTreeKey, profile); if (NS_FAILED(rv)) return rv; } else { nsRegistryKey profKey; ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); rv = node->GetKey(&profKey); if (NS_FAILED(rv)) return rv; rv = registry->SetString(profKey, kRegistryMigratedString.get(), profileItem->isMigrated ? kRegistryYesString.get() : kRegistryNoString.get()); if (NS_FAILED(rv)) return rv; registry->SetString(profKey, kRegistryNCProfileNameString.get(), profileItem->NCProfileName.get()); registry->SetString(profKey, kRegistryNCServiceDenialString.get(), profileItem->NCDeniedService.get()); registry->SetString(profKey, kRegistryNCUserEmailString.get(), profileItem->NCEmailAddress.get()); registry->SetString(profKey, kRegistryNCHavePREGInfoString.get(), profileItem->NCHavePregInfo.get()); registry->SetLongLong(profKey, kRegistryCreationTimeString.get(), &profileItem->creationTime); registry->SetLongLong(profKey, kRegistryLastModTimeString.get(), &profileItem->lastModTime); rv = profileItem->ExternalizeLocation(registry, profKey); if (NS_FAILED(rv)) { NS_ASSERTION(PR_FALSE, "Could not update profile location"); rv = enumKeys->Next(); if (NS_FAILED(rv)) return rv; continue; } profileItem->ExternalizeMigratedFromLocation(registry, profKey); profileItem->updateProfileEntry = PR_FALSE; } rv = enumKeys->Next(); if (NS_FAILED(rv)) return rv; } // Take care of new nodes PRInt32 numElems = mProfiles->Count(); for (int i = 0; i < numElems; i++) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(i)); if (!profileItem->isImportType && profileItem->updateProfileEntry) { nsRegistryKey profKey; rv = registry->AddKey(profilesTreeKey, profileItem->profileName.get(), &profKey); if (NS_FAILED(rv)) return rv; rv = registry->SetString(profKey, kRegistryMigratedString.get(), profileItem->isMigrated ? kRegistryYesString.get() : kRegistryNoString.get()); if (NS_FAILED(rv)) return rv; registry->SetString(profKey, kRegistryNCProfileNameString.get(), profileItem->NCProfileName.get()); registry->SetString(profKey, kRegistryNCServiceDenialString.get(), profileItem->NCDeniedService.get()); registry->SetString(profKey, kRegistryNCUserEmailString.get(), profileItem->NCEmailAddress.get()); registry->SetString(profKey, kRegistryNCHavePREGInfoString.get(), profileItem->NCHavePregInfo.get()); registry->SetLongLong(profKey, kRegistryCreationTimeString.get(), &profileItem->creationTime); registry->SetLongLong(profKey, kRegistryLastModTimeString.get(), &profileItem->lastModTime); rv = profileItem->ExternalizeLocation(registry, profKey); if (NS_FAILED(rv)) { NS_ASSERTION(PR_FALSE, "Could not update profile location"); continue; } profileItem->ExternalizeMigratedFromLocation(registry, profKey); profileItem->updateProfileEntry = PR_FALSE; } } mProfileDataChanged = PR_FALSE; return rv; } nsresult nsProfileAccess::GetOriginalProfileDir(const PRUnichar *profileName, nsILocalFile **originalDir) { NS_ENSURE_ARG(profileName); NS_ENSURE_ARG_POINTER(originalDir); *originalDir = nsnull; nsresult rv = NS_OK; PRInt32 index = FindProfileIndex(profileName, PR_TRUE); if (index >= 0) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); nsCOMPtr profileDir; rv = profileItem->GetResolvedProfileDir(getter_AddRefs(profileDir)); if (NS_SUCCEEDED(rv) && profileDir) { #ifdef XP_MAC PRBool exists; rv = profileDir->Exists(&exists); if (NS_FAILED(rv)) return rv; if (exists) { PRBool inTrash; nsCOMPtr trashFolder; rv = NS_GetSpecialDirectory(NS_MAC_TRASH_DIR, getter_AddRefs(trashFolder)); if (NS_FAILED(rv)) return rv; rv = trashFolder->Contains(profileDir, PR_TRUE, &inTrash); if (NS_FAILED(rv)) return rv; if (inTrash) { return NS_ERROR_FILE_NOT_FOUND; } } #endif NS_IF_ADDREF(*originalDir = profileDir); } return rv; } return NS_ERROR_FAILURE; } nsresult nsProfileAccess::SetMigratedFromDir(const PRUnichar *profileName, nsILocalFile *originalDir) { NS_ENSURE_ARG(profileName); NS_ENSURE_ARG(originalDir); PRInt32 index = FindProfileIndex(profileName, PR_FALSE); if (index >= 0) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); profileItem->migratedFrom = originalDir; profileItem->updateProfileEntry = PR_TRUE; return NS_OK; } return NS_ERROR_FAILURE; } nsresult nsProfileAccess::SetProfileLastModTime(const PRUnichar *profileName, PRInt64 lastModTime) { NS_ENSURE_ARG(profileName); PRInt32 index = FindProfileIndex(profileName, PR_FALSE); if (index >= 0) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); profileItem->lastModTime = lastModTime; profileItem->updateProfileEntry = PR_TRUE; return NS_OK; } return NS_ERROR_FAILURE; } // Return the list of profiles, 4x, 5x, or both. nsresult nsProfileAccess::GetProfileList(PRInt32 whichKind, PRUint32 *length, PRUnichar ***result) { NS_ENSURE_ARG_POINTER(length); *length = 0; NS_ENSURE_ARG_POINTER(result); *result = nsnull; nsresult rv = NS_OK; PRInt32 count, localLength = 0; PRUnichar **outArray, **next; PRInt32 numElems = mProfiles->Count(); PRInt32 profilesCount; switch (whichKind) { case nsIProfileInternal::LIST_ONLY_NEW: GetNumProfiles(&count); break; case nsIProfileInternal::LIST_ONLY_OLD: GetNum4xProfiles(&count); break; case nsIProfileInternal::LIST_ALL: GetNum4xProfiles(&count); GetNumProfiles(&profilesCount); count += profilesCount; break; case nsIProfileInternal::LIST_FOR_IMPORT: GetNum4xProfiles(&count); GetNumProfiles(&profilesCount); count = numElems - (count + profilesCount); break; default: NS_ASSERTION(PR_FALSE, "Bad parameter"); return NS_ERROR_INVALID_ARG; } next = outArray = (PRUnichar **)nsMemory::Alloc(count * sizeof(PRUnichar *)); if (!outArray) return NS_ERROR_OUT_OF_MEMORY; for (PRInt32 index=0; index < numElems && localLength < count; index++) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); if (whichKind == nsIProfileInternal::LIST_ONLY_OLD && (profileItem->isMigrated || profileItem->isImportType)) continue; else if (whichKind == nsIProfileInternal::LIST_ONLY_NEW && (!profileItem->isMigrated || profileItem->isImportType)) continue; else if (whichKind == nsIProfileInternal::LIST_ALL && profileItem->isImportType) continue; else if (whichKind == nsIProfileInternal::LIST_FOR_IMPORT && !profileItem->isImportType) continue; *next = ToNewUnicode(profileItem->profileName); if (*next == nsnull) { rv = NS_ERROR_OUT_OF_MEMORY; break; } next++; localLength++; } if (NS_SUCCEEDED(rv)) { *result = outArray; *length = localLength; } else { while (--next >= outArray) nsMemory::Free(*next); nsMemory::Free(outArray); } return rv; } // Return a boolean based on the profile existence. PRBool nsProfileAccess::ProfileExists(const PRUnichar *profileName) { NS_ASSERTION(profileName, "Invalid profile name"); PRBool exists = PR_FALSE; PRInt32 numElems = mProfiles->Count(); for (PRInt32 index=0; index < numElems; index++) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); if (!profileItem->isImportType && profileItem->profileName.Equals(profileName)) { exists = PR_TRUE; break; } } return exists; } // Capture the 4x profile information from the old registry (4x) nsresult nsProfileAccess::Get4xProfileInfo(const char *registryName, PRBool fromImport) { nsresult rv = NS_OK; if (fromImport && m4xProfilesAdded) return rv; nsAutoString charSet; rv = GetPlatformCharset(charSet); if (NS_FAILED(rv)) return rv; #if defined(XP_PC) || defined(XP_MAC) || defined(XP_MACOSX) NS_ASSERTION(registryName, "Invalid registryName"); nsCOMPtr registryFile; rv = NS_NewNativeLocalFile(nsDependentCString(registryName), PR_TRUE, getter_AddRefs(registryFile)); if (NS_FAILED(rv)) return rv; nsCOMPtr oldReg(do_CreateInstance(NS_REGISTRY_CONTRACTID, &rv)); if (NS_FAILED(rv)) return rv; rv = oldReg->Open(registryFile); if (NS_FAILED(rv)) return rv; // Enumerate 4x tree and create an array of that information. // Enumerate all subkeys (immediately) under the given node. nsCOMPtr enumKeys; rv = oldReg->EnumerateSubtrees(nsIRegistry::Users, getter_AddRefs(enumKeys)); if (NS_FAILED(rv)) return rv; rv = enumKeys->First(); if (NS_FAILED(rv)) return rv; if (fromImport) m4xProfilesAdded = PR_TRUE; // Enumerate subkeys till done. while( (NS_OK != enumKeys->IsDone())) { nsCOMPtr base; rv = enumKeys->CurrentItem(getter_AddRefs(base)); if (NS_FAILED(rv)) return rv; // Get specific interface. nsCOMPtr node; nsIID nodeIID = NS_IREGISTRYNODE_IID; rv = base->QueryInterface( nodeIID, getter_AddRefs(node)); if (NS_FAILED(rv)) return rv; nsXPIDLString profile; rv = node->GetName(getter_Copies(profile)); if (NS_FAILED(rv)) return rv; // Unescape is done on the profileName to interpret special characters like %, _ etc. // For example something like %20 would probably be interpreted as a space // There is some problem I guess in sending a space as itself // NOTE: This needs to be done BEFORE the test for existence. #if defined(XP_MAC) || defined(XP_MACOSX) // 4.x profiles coming from japanese machine are already in unicode. // So, there is no need to decode into unicode further. NS_ConvertUCS2toUTF8 temp(profile); nsCAutoString profileName(nsUnescape( NS_CONST_CAST(char*, temp.get()))); nsAutoString convertedProfName(NS_ConvertUTF8toUCS2(profileName).get()); #else nsCAutoString temp; temp.AssignWithConversion(profile); nsCAutoString profileName(nsUnescape( NS_CONST_CAST(char*, temp.get()))); nsAutoString convertedProfName; ConvertStringToUnicode(charSet, profileName.get(), convertedProfName); #endif PRBool exists = PR_FALSE; if (!fromImport) { exists = ProfileExists(convertedProfName.get()); if (exists) { rv = enumKeys->Next(); if (NS_FAILED(rv)) return rv; continue; } } nsRegistryKey key; rv = node->GetKey(&key); if (NS_FAILED(rv)) return rv; ProfileStruct* profileItem = new ProfileStruct(); if (!profileItem) return NS_ERROR_OUT_OF_MEMORY; profileItem->updateProfileEntry = PR_TRUE; profileItem->profileName = convertedProfName; rv = profileItem->InternalizeLocation(oldReg, key, PR_TRUE); NS_ASSERTION(NS_SUCCEEDED(rv), "Could not get 4x profile location"); profileItem->isMigrated = PR_FALSE; profileItem->isImportType = fromImport; SetValue(profileItem); rv = enumKeys->Next(); if (NS_FAILED(rv)) return rv; } #elif defined (XP_BEOS) #else /* XP_UNIX */ nsCAutoString unixProfileName( PR_GetEnv(PROFILE_NAME_ENVIRONMENT_VARIABLE) ); nsCAutoString unixProfileDirectory( PR_GetEnv(PROFILE_HOME_ENVIRONMENT_VARIABLE) ); if (unixProfileName.IsEmpty() || unixProfileDirectory.IsEmpty()) { unixProfileDirectory = PR_GetEnv(HOME_ENVIRONMENT_VARIABLE); unixProfileName = PR_GetEnv(LOGNAME_ENVIRONMENT_VARIABLE); if ( unixProfileName.IsEmpty() ) { unixProfileName = PR_GetEnv(USER_ENVIRONMENT_VARIABLE); } if ( unixProfileName.IsEmpty() ) { unixProfileName = DEFAULT_UNIX_PROFILE_NAME; } } PRBool exists = PR_FALSE; if (!fromImport) { exists = ProfileExists(NS_ConvertASCIItoUCS2(unixProfileName).get()); if (exists) { return NS_OK; } } if ( ! unixProfileName.IsEmpty() && ! unixProfileDirectory.IsEmpty() ) { nsCAutoString profileLocation(unixProfileDirectory); profileLocation += "/.netscape"; nsCOMPtr users4xDotNetscapeDirectory; rv = NS_NewFileSpec(getter_AddRefs(users4xDotNetscapeDirectory)); if (NS_FAILED(rv)) return rv; rv = users4xDotNetscapeDirectory->SetNativePath(profileLocation.get()); if (NS_FAILED(rv)) return rv; rv = users4xDotNetscapeDirectory->Exists(&exists); if (NS_FAILED(rv)) return rv; #ifdef DEBUG printf("%s exists: %d\n",profileLocation.get(), exists); #endif if (exists) { ProfileStruct* profileItem = new ProfileStruct(); if (!profileItem) return NS_ERROR_OUT_OF_MEMORY; profileItem->updateProfileEntry = PR_TRUE; profileItem->profileName = NS_ConvertASCIItoUCS2(unixProfileName).get(); nsCOMPtr localFile; rv = NS_NewNativeLocalFile(profileLocation, PR_TRUE, getter_AddRefs(localFile)); if (NS_FAILED(rv)) return rv; profileItem->SetResolvedProfileDir(localFile); profileItem->isMigrated = PR_FALSE; profileItem->isImportType = fromImport; SetValue(profileItem); } else { #ifdef DEBUG printf("no 4.x profile\n"); #endif } } #endif /* XP_UNIX */ return rv; } // Set the PREG flag to indicate if that info exists void nsProfileAccess::SetPREGInfo(const char* pregInfo) { NS_ASSERTION(pregInfo, "Invalid pregInfo"); // This is always going to be just a yes/no string mHavePREGInfo.AssignWithConversion(pregInfo); } //Get the for PREG info. void nsProfileAccess::CheckRegString(const PRUnichar *profileName, char **info) { NS_ASSERTION(profileName, "Invalid profile name"); NS_ASSERTION(info, "Invalid info pointer"); *info = nsnull; PRInt32 index = 0; index = FindProfileIndex(profileName, PR_FALSE); if (index >= 0 ) { ProfileStruct* profileItem = (ProfileStruct *) (mProfiles->ElementAt(index)); if (!profileItem->NCHavePregInfo.IsEmpty()) { *info = ToNewCString(profileItem->NCHavePregInfo); } else { *info = ToNewCString(kRegistryNoString); } } } // Clear the profile member data structure // We need to fill in the data from the new registry nsresult nsProfileAccess::ResetProfileMembers() { FreeProfileMembers(mProfiles); mProfiles = new nsVoidArray(); return NS_OK; } nsresult nsProfileAccess::DetermineForceMigration(PRBool *forceMigration) { if (!forceMigration) return NS_ERROR_NULL_POINTER; PRInt32 numProfiles; GetNumProfiles(&numProfiles); if (numProfiles > 0) { // we have some 6.0 profiles, don't force migration: *forceMigration = PR_FALSE; return NS_OK; } // even if we don't any 4.x profiles, running -installer is safe. so do it *forceMigration = PR_TRUE; return NS_OK; } // ********************************************************************** // class ProfileStruct // ********************************************************************** ProfileStruct::ProfileStruct() : isMigrated(PR_FALSE), updateProfileEntry(PR_FALSE), isImportType(PR_FALSE), creationTime(LL_ZERO), lastModTime(LL_ZERO) { } ProfileStruct::ProfileStruct(const ProfileStruct& src) { *this = src; } ProfileStruct& ProfileStruct::operator=(const ProfileStruct& rhs) { profileName = rhs.profileName; isMigrated = rhs.isMigrated; NCProfileName = rhs.NCProfileName; NCDeniedService = rhs.NCDeniedService; NCEmailAddress = rhs.NCEmailAddress; NCHavePregInfo = rhs.NCHavePregInfo; updateProfileEntry = rhs.updateProfileEntry; isImportType = rhs.isImportType; creationTime = rhs.creationTime; lastModTime = rhs.lastModTime; nsresult rv; nsCOMPtr file; resolvedLocation = nsnull; if (rhs.resolvedLocation) { regLocationData.Truncate(0); rv = rhs.resolvedLocation->Clone(getter_AddRefs(file)); if (NS_SUCCEEDED(rv)) resolvedLocation = do_QueryInterface(file); file = nsnull; } else regLocationData = rhs.regLocationData; migratedFrom = nsnull; if (rhs.migratedFrom) { rv = rhs.migratedFrom->Clone(getter_AddRefs(file)); if (NS_SUCCEEDED(rv)) migratedFrom = do_QueryInterface(file); } return *this; } nsresult ProfileStruct::GetResolvedProfileDir(nsILocalFile **aDirectory) { NS_ENSURE_ARG_POINTER(aDirectory); *aDirectory = nsnull; if (resolvedLocation) { *aDirectory = resolvedLocation; NS_ADDREF(*aDirectory); return NS_OK; } return NS_ERROR_FILE_NOT_FOUND; // The only reason it would not exist. } nsresult ProfileStruct::SetResolvedProfileDir(nsILocalFile *aDirectory) { NS_ENSURE_ARG(aDirectory); resolvedLocation = aDirectory; regLocationData.SetLength(0); return NS_OK; } nsresult ProfileStruct::CopyProfileLocation(ProfileStruct *destStruct) { if (resolvedLocation) { nsCOMPtr file; nsresult rv = resolvedLocation->Clone(getter_AddRefs(file)); if (NS_SUCCEEDED(rv)) destStruct->resolvedLocation = do_QueryInterface(file, &rv); if (NS_FAILED(rv)) return rv; } destStruct->regLocationData = regLocationData; return NS_OK; } nsresult ProfileStruct::InternalizeLocation(nsIRegistry *aRegistry, nsRegistryKey profKey, PRBool is4x) { nsresult rv; nsCOMPtr tempLocal; // Reset ourselves regLocationData.SetLength(0); resolvedLocation = nsnull; if (is4x) { nsXPIDLString profLoc; rv = aRegistry->GetString( profKey, NS_LITERAL_STRING("ProfileLocation").get(), getter_Copies(profLoc)); if (NS_FAILED(rv)) return rv; regLocationData = profLoc; #if defined(XP_MAC) || defined(XP_MACOSX) // 4.x profiles coming from japanese machine are already in unicode. // So, there is no need to decode into unicode further. // Unescape profile location NS_ConvertUCS2toUTF8 tempLoc(profLoc); nsCAutoString profileLocation(nsUnescape( NS_CONST_CAST(char*, tempLoc.get()))); nsAutoString convertedProfLoc(NS_ConvertUTF8toUCS2(profileLocation).get()); #else nsAutoString charSet; rv = GetPlatformCharset(charSet); if (NS_FAILED(rv)) return rv; // Unescape profile location and convert it to the right format nsCAutoString tempLoc; tempLoc.AssignWithConversion(profLoc); nsCAutoString profileLocation(nsUnescape( NS_CONST_CAST(char*, tempLoc.get()))); nsAutoString convertedProfLoc; ConvertStringToUnicode(charSet, profileLocation.get(), convertedProfLoc); #endif // Now we have a unicode path - make it into a file rv = NS_NewLocalFile(convertedProfLoc, PR_TRUE, getter_AddRefs(tempLocal)); } else { nsXPIDLString regData; rv = aRegistry->GetString(profKey, kRegistryDirectoryString.get(), getter_Copies(regData)); if (NS_FAILED(rv)) return rv; regLocationData = regData; #if defined(XP_MAC) || defined(XP_MACOSX) // For a brief time, this was a unicode path PRInt32 firstColon = regLocationData.FindChar(PRUnichar(':')); if (firstColon == -1) { rv = NS_NewNativeLocalFile(nsCString(), PR_TRUE, getter_AddRefs(tempLocal)); if (NS_SUCCEEDED(rv)) // XXX this only works on XP_MAC because regLocationData is ASCII rv = tempLocal->SetPersistentDescriptor(NS_ConvertUCS2toUTF8(regLocationData)); } else #endif rv = NS_NewLocalFile(regLocationData, PR_TRUE, getter_AddRefs(tempLocal)); } if (NS_SUCCEEDED(rv) && tempLocal) { PRBool exists; if (NS_SUCCEEDED(tempLocal->Exists(&exists)) && exists) SetResolvedProfileDir(tempLocal); } return NS_OK; } nsresult ProfileStruct::ExternalizeLocation(nsIRegistry *aRegistry, nsRegistryKey profKey) { nsresult rv; if (resolvedLocation) { nsAutoString regData; #if defined(XP_MAC) || defined(XP_MACOSX) PRBool leafCreated; nsCAutoString descBuf; // It must exist before we try to use GetPersistentDescriptor rv = EnsureDirPathExists(resolvedLocation, &leafCreated); if (NS_FAILED(rv)) return rv; rv = resolvedLocation->GetPersistentDescriptor(descBuf); if (NS_FAILED(rv)) return rv; if (leafCreated) resolvedLocation->Remove(PR_FALSE); regData = NS_ConvertUTF8toUCS2(descBuf); #else rv = resolvedLocation->GetPath(regData); if (NS_FAILED(rv)) return rv; #endif rv = aRegistry->SetString(profKey, kRegistryDirectoryString.get(), regData.get()); } else if (regLocationData.Length() != 0) { // Write the original data back out - maybe it can be resolved later. rv = aRegistry->SetString(profKey, kRegistryDirectoryString.get(), regLocationData.get()); } else { NS_ASSERTION(PR_FALSE, "ProfileStruct has no location data!"); rv = NS_ERROR_FAILURE; } return rv; } nsresult ProfileStruct::InternalizeMigratedFromLocation(nsIRegistry *aRegistry, nsRegistryKey profKey) { nsresult rv; nsXPIDLCString regData; nsCOMPtr tempLocal; rv = aRegistry->GetStringUTF8(profKey, kRegistryMigratedFromString.get(), getter_Copies(regData)); if (NS_SUCCEEDED(rv)) { #if defined(XP_MAC) || defined(XP_MACOSX) rv = NS_NewLocalFile(nsString(), PR_TRUE, getter_AddRefs(tempLocal)); if (NS_SUCCEEDED(rv)) { // The persistent desc on Mac is base64 encoded so plain ASCII rv = tempLocal->SetPersistentDescriptor(regData); if (NS_SUCCEEDED(rv)) migratedFrom = tempLocal; } #else rv = NS_NewLocalFile(NS_ConvertUTF8toUCS2(regData), PR_TRUE, getter_AddRefs(tempLocal)); if (NS_SUCCEEDED(rv)) migratedFrom = tempLocal; #endif } return rv; } nsresult ProfileStruct::ExternalizeMigratedFromLocation(nsIRegistry *aRegistry, nsRegistryKey profKey) { nsresult rv = NS_OK; nsCAutoString regData; if (migratedFrom) { #if defined(XP_MAC) || defined(XP_MACOSX) rv = migratedFrom->GetPersistentDescriptor(regData); #else nsAutoString path; rv = migratedFrom->GetPath(path); regData = NS_ConvertUCS2toUTF8(path); #endif if (NS_SUCCEEDED(rv)) rv = aRegistry->SetStringUTF8(profKey, kRegistryMigratedFromString.get(), regData.get()); } return rv; } nsresult ProfileStruct::EnsureDirPathExists(nsILocalFile *aDir, PRBool *wasCreated) { NS_ENSURE_ARG(aDir); NS_ENSURE_ARG_POINTER(wasCreated); *wasCreated = PR_FALSE; nsresult rv; PRBool exists; rv = aDir->Exists(&exists); if (NS_SUCCEEDED(rv) && !exists) { rv = aDir->Create(nsIFile::DIRECTORY_TYPE, 0775); *wasCreated = NS_SUCCEEDED(rv); } return rv; } // ********************************************************************** // class nsProfileLock // ********************************************************************** #if defined (XP_MAC) && TARGET_CARBON // The following functions and definitions are used in order to allow // a CFM build to use the same locking scheme as a Mach-0 build. static CFBundleRef getBundle(CFStringRef frameworkPath) { CFBundleRef bundle = NULL; // Make a CFURLRef from the CFString representation of the bundle's path. // See the Core Foundation URL Services chapter for details. CFURLRef bundleURL = CFURLCreateWithFileSystemPath(NULL, frameworkPath, kCFURLPOSIXPathStyle, true); if (bundleURL != NULL) { bundle = CFBundleCreate(NULL, bundleURL); if (bundle != NULL) CFBundleLoadExecutable(bundle); CFRelease(bundleURL); } return bundle; } static void* getSystemFunction(CFStringRef functionName) { static CFBundleRef systemBundle = getBundle(CFSTR("/System/Library/Frameworks/System.framework")); if (systemBundle) return CFBundleGetFunctionPointerForName(systemBundle, functionName); return NULL; } // From typedef int64_t off_t; /* file offset */ typedef int32_t pid_t; /* process id */ // From #define EAGAIN 35 /* Resource temporarily unavailable */ #define EACCES 13 /* Permission denied */ // From #define O_RDONLY 0x0000 /* open for reading only */ #define O_WRONLY 0x0001 /* open for writing only */ #define O_RDWR 0x0002 /* open for reading and writing */ #define O_CREAT 0x0200 /* create if nonexistant */ #define O_TRUNC 0x0400 /* truncate to zero length */ #define O_EXCL 0x0800 /* error if already exists */ #define F_RDLCK 1 /* shared or read lock */ #define F_UNLCK 2 /* unlock */ #define F_WRLCK 3 /* exclusive or write lock */ #define F_SETLK 8 /* set record locking information */ struct flock { off_t l_start; /* starting offset */ off_t l_len; /* len = 0 means until end of file */ pid_t l_pid; /* lock owner */ short l_type; /* lock type: read/write, etc. */ short l_whence; /* type of l_start */ }; // Proc ptr types of the procs we import from System framework typedef int (*open_proc_ptr) (const char *, int, ...); typedef int (*close_proc_ptr) (int); typedef int (*fcntl_proc_ptr) (int, int, ...); typedef int* (*error_proc_ptr) (); static open_proc_ptr bsd_open = (open_proc_ptr) getSystemFunction(CFSTR("open")); static close_proc_ptr bsd_close = (close_proc_ptr) getSystemFunction(CFSTR("close")); static fcntl_proc_ptr bsd_fcntl = (fcntl_proc_ptr) getSystemFunction(CFSTR("fcntl")); static error_proc_ptr bsd_error = (error_proc_ptr) getSystemFunction(CFSTR("__error")); #endif /* XP_MAC && TARGET_CARBON */ nsProfileLock::nsProfileLock() : mHaveLock(PR_FALSE) #if defined (XP_MAC) && TARGET_CARBON ,mLockFileDesc(-1) #elif defined (XP_WIN) ,mLockFileHandle(INVALID_HANDLE_VALUE) #elif defined (XP_OS2) ,mLockFileHandle(-1) #elif defined (XP_UNIX) ,mPidLockFileName(nsnull) ,mLockFileDesc(-1) #endif { #if defined (XP_UNIX) next = prev = this; #endif } nsProfileLock::nsProfileLock(nsProfileLock& src) { *this = src; } nsProfileLock& nsProfileLock::operator=(nsProfileLock& rhs) { Unlock(); mHaveLock = rhs.mHaveLock; rhs.mHaveLock = PR_FALSE; #if defined (XP_MAC) mLockFile = rhs.mLockFile; rhs.mLockFile = nsnull; #if TARGET_CARBON mLockFileDesc = rhs.mLockFileDesc; rhs.mLockFileDesc = -1; #endif #elif defined (XP_WIN) mLockFileHandle = rhs.mLockFileHandle; rhs.mLockFileHandle = INVALID_HANDLE_VALUE; #elif defined (XP_OS2) mLockFileHandle = rhs.mLockFileHandle; rhs.mLockFileHandle = -1; #elif defined (XP_UNIX) mLockFileDesc = rhs.mLockFileDesc; rhs.mLockFileDesc = -1; mPidLockFileName = rhs.mPidLockFileName; rhs.mPidLockFileName = nsnull; if (mPidLockFileName) { // rhs had a symlink lock, therefore it was on the list. PR_REMOVE_LINK(&rhs); PR_APPEND_LINK(this, &mPidLockList); } #endif return *this; } nsProfileLock::~nsProfileLock() { Unlock(); } #if defined (XP_UNIX) static int setupPidLockCleanup; PRCList nsProfileLock::mPidLockList = PR_INIT_STATIC_CLIST(&nsProfileLock::mPidLockList); void nsProfileLock::RemovePidLockFiles() { while (!PR_CLIST_IS_EMPTY(&mPidLockList)) { nsProfileLock *lock = NS_STATIC_CAST(nsProfileLock*, mPidLockList.next); lock->Unlock(); } } static struct sigaction SIGHUP_oldact; static struct sigaction SIGINT_oldact; static struct sigaction SIGQUIT_oldact; static struct sigaction SIGILL_oldact; static struct sigaction SIGABRT_oldact; static struct sigaction SIGSEGV_oldact; static struct sigaction SIGTERM_oldact; #ifdef HAVE_SIGINFO_T // There is no standard type definition for the type of sa_sigaction. extern "C" { typedef void (*my_sigaction_t)(int, siginfo_t*, void*); } void nsProfileLock::FatalSignalHandler(int signo, siginfo_t* info, void* context) #else void nsProfileLock::FatalSignalHandler(int signo) #endif { // Remove any locks still held. RemovePidLockFiles(); // Chain to the old handler, which may exit. struct sigaction *oldact = nsnull; switch (signo) { case SIGHUP: oldact = &SIGHUP_oldact; break; case SIGINT: oldact = &SIGINT_oldact; break; case SIGQUIT: oldact = &SIGQUIT_oldact; break; case SIGILL: oldact = &SIGILL_oldact; break; case SIGABRT: oldact = &SIGABRT_oldact; break; case SIGSEGV: oldact = &SIGSEGV_oldact; break; case SIGTERM: oldact = &SIGTERM_oldact; break; default: NS_NOTREACHED("bad signo"); break; } if (oldact) { #ifdef HAVE_SIGINFO_T if (oldact->sa_flags & SA_SIGINFO) { if (oldact->sa_sigaction && oldact->sa_sigaction != (my_sigaction_t) SIG_DFL && oldact->sa_sigaction != (my_sigaction_t) SIG_IGN) { oldact->sa_sigaction(signo, info, context); } } else #endif { if (oldact->sa_handler && oldact->sa_handler != SIG_DFL && oldact->sa_handler != SIG_IGN) { oldact->sa_handler(signo); } } } // Backstop exit call, just in case. _exit(signo); } #endif /* XP_UNIX */ nsresult nsProfileLock::Lock(nsILocalFile* aFile) { #if defined (XP_MAC) || defined (XP_MACOSX) NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock"); NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "parent.lock"); #elif defined (XP_UNIX) NS_NAMED_LITERAL_STRING(OLD_LOCKFILE_NAME, "lock"); NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, ".parentlock"); #else NS_NAMED_LITERAL_STRING(LOCKFILE_NAME, "parent.lock"); #endif nsresult rv; NS_ENSURE_STATE(!mHaveLock); PRBool isDir; rv = aFile->IsDirectory(&isDir); if (NS_FAILED(rv)) return rv; if (!isDir) return NS_ERROR_FILE_NOT_DIRECTORY; nsCOMPtr lockFile; rv = aFile->Clone((nsIFile **)((void **)getter_AddRefs(lockFile))); if (NS_FAILED(rv)) return rv; rv = lockFile->Append(LOCKFILE_NAME); if (NS_FAILED(rv)) return rv; #if defined(XP_MAC) || defined(XP_MACOSX) #if defined(XP_MACOSX) nsCAutoString filePath; rv = lockFile->GetNativePath(filePath); if (NS_FAILED(rv)) return rv; mLockFileDesc = open(filePath.get(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (mLockFileDesc == -1) { NS_ERROR("Failed to open lock file."); return NS_ERROR_FAILURE; } struct flock lock; lock.l_start = 0; lock.l_len = 0; // len = 0 means entire file lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; if (fcntl(mLockFileDesc, F_SETLK, &lock) == -1) { if (errno == EAGAIN || errno == EACCES) return NS_ERROR_FILE_ACCESS_DENIED; return NS_ERROR_FAILURE; } mHaveLock = PR_TRUE; #elif TARGET_CARBON if (bsd_open && bsd_close && bsd_fcntl && bsd_error) // FALSE for TARGET_CARBON on OS9 { // We need a UTF-8 Posix path to pass to open. CFURLRef lockFileCFURL; nsCOMPtr lockMacFile(do_QueryInterface(lockFile)); if (!lockMacFile) return NS_ERROR_FAILURE; rv = lockMacFile->GetCFURL(&lockFileCFURL); if (NS_FAILED(rv)) return rv; // On OSX, even if we're a CFM app, this will return a UTF-8 Posix path. Boolean gotPath; UInt8 pathBuf[1024]; gotPath = ::CFURLGetFileSystemRepresentation(lockFileCFURL, false, pathBuf, sizeof(pathBuf)); ::CFRelease(lockFileCFURL); if (!gotPath) return NS_ERROR_FAILURE; mLockFileDesc = bsd_open((char *)pathBuf, O_WRONLY | O_CREAT | O_TRUNC, 0666); if (mLockFileDesc == -1) { NS_ERROR("Failed to open lock file."); return NS_ERROR_FAILURE; } struct flock lock; lock.l_start = 0; lock.l_len = 0; // len = 0 means entire file lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; if (bsd_fcntl(mLockFileDesc, F_SETLK, &lock) == -1) { int *errno_ptr = bsd_error(); if (*errno_ptr == EAGAIN || *errno_ptr == EACCES) return NS_ERROR_FILE_ACCESS_DENIED; return NS_ERROR_FAILURE; } mHaveLock = PR_TRUE; } #endif /* TARGET_CARBON */ // Even if we're using fcntl locking, check for the old-style lock. struct LockProcessInfo { ProcessSerialNumber psn; unsigned long launchDate; }; PRFileDesc *fd = nsnull; PRInt32 ioBytes; ProcessInfoRec processInfo; LockProcessInfo lockProcessInfo; rv = lockFile->SetLeafName(OLD_LOCKFILE_NAME); if (NS_FAILED(rv)) return rv; rv = lockFile->OpenNSPRFileDesc(PR_RDONLY, 0, &fd); if (NS_SUCCEEDED(rv)) { ioBytes = PR_Read(fd, &lockProcessInfo, sizeof(LockProcessInfo)); PR_Close(fd); if (ioBytes == sizeof(LockProcessInfo)) { processInfo.processAppSpec = nsnull; processInfo.processName = nsnull; processInfo.processInfoLength = sizeof(ProcessInfoRec); if (::GetProcessInformation(&lockProcessInfo.psn, &processInfo) == noErr && processInfo.processLaunchDate == lockProcessInfo.launchDate) { return NS_ERROR_FILE_ACCESS_DENIED; } } else { NS_WARNING("Could not read lock file - ignoring lock"); } } rv = NS_OK; // Don't propagate error from OpenNSPRFileDesc. #if !TARGET_CARBON rv = lockFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0, &fd); if (NS_FAILED(rv)) return rv; static LockProcessInfo sSelfInfo; static PRBool sSelfInfoInited; if (!sSelfInfoInited) { ProcessSerialNumber psn; if (::GetCurrentProcess(&psn) == noErr) { processInfo.processAppSpec = nsnull; processInfo.processName = nsnull; processInfo.processInfoLength = sizeof(ProcessInfoRec); if (::GetProcessInformation(&psn, &processInfo) == noErr) { sSelfInfo.psn = processInfo.processNumber; sSelfInfo.launchDate = processInfo.processLaunchDate; sSelfInfoInited = PR_TRUE; } } } if (!sSelfInfoInited) { PR_Close(fd); return NS_ERROR_FAILURE; } ioBytes = PR_Write(fd, &sSelfInfo, sizeof(LockProcessInfo)); PR_Close(fd); if (ioBytes != sizeof(LockProcessInfo)) return NS_ERROR_FAILURE; mLockFile = lockFile; #endif /* !TARGET_CARBON */ #elif defined(XP_WIN) nsCAutoString filePath; rv = lockFile->GetNativePath(filePath); if (NS_FAILED(rv)) return rv; mLockFileHandle = CreateFile(filePath.get(), GENERIC_READ | GENERIC_WRITE, 0, // no sharing - of course nsnull, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nsnull); if (mLockFileHandle == INVALID_HANDLE_VALUE) return NS_ERROR_FILE_ACCESS_DENIED; #elif defined(XP_OS2) nsCAutoString filePath; rv = lockFile->GetNativePath(filePath); if (NS_FAILED(rv)) return rv; ULONG ulAction = 0; APIRET rc; rc = DosOpen(filePath.get(), &mLockFileHandle, &ulAction, 0, FILE_NORMAL, OPEN_ACTION_CREATE_IF_NEW | OPEN_ACTION_OPEN_IF_EXISTS, OPEN_ACCESS_READWRITE | OPEN_SHARE_DENYREADWRITE | OPEN_FLAGS_NOINHERIT, 0 ); if (rc != NO_ERROR) { mLockFileHandle = -1; return NS_ERROR_FILE_ACCESS_DENIED; } #elif defined(VMS) nsCAutoString filePath; rv = lockFile->GetNativePath(filePath); if (NS_FAILED(rv)) return rv; mLockFileDesc = open_noshr(filePath.get(), O_CREAT, 0666); if (mLockFileDesc == -1) { if ((errno == EVMSERR) && (vaxc$errno == RMS$_FLK)) { return NS_ERROR_FILE_ACCESS_DENIED; } else { NS_ERROR("Failed to open lock file."); return NS_ERROR_FAILURE; } } #elif defined(XP_UNIX) #ifdef USE_SYMLINK_LOCKING nsCOMPtr oldLockFile; rv = aFile->Clone((nsIFile **)((void **)getter_AddRefs(oldLockFile))); if (NS_FAILED(rv)) return rv; rv = oldLockFile->Append(OLD_LOCKFILE_NAME); if (NS_FAILED(rv)) return rv; nsCAutoString oldFilePath; rv = oldLockFile->GetNativePath(oldFilePath); if (NS_FAILED(rv)) return rv; // First, try the 4.x-compatible symlink technique, which works with NFS // without depending on (broken or missing, too often) lockd. struct in_addr inaddr; inaddr.s_addr = INADDR_LOOPBACK; char hostname[256]; PRStatus status = PR_GetSystemInfo(PR_SI_HOSTNAME, hostname, sizeof hostname); if (status == PR_SUCCESS) { char netdbbuf[PR_NETDB_BUF_SIZE]; PRHostEnt hostent; status = PR_GetHostByName(hostname, netdbbuf, sizeof netdbbuf, &hostent); if (status == PR_SUCCESS) memcpy(&inaddr, hostent.h_addr, sizeof inaddr); } char *signature = PR_smprintf("%s:%lu", inet_ntoa(inaddr), (unsigned long)getpid()); const char *oldFileName = oldFilePath.get(); int symlink_rv, symlink_errno, tries = 0; // use ns4.x-compatible symlinks if the FS supports them while ((symlink_rv = symlink(signature, oldFileName)) < 0) { symlink_errno = errno; if (symlink_errno != EEXIST) break; // the link exists; see if it's from this machine, and if // so if the process is still active char buf[1024]; int len = readlink(oldFileName, buf, sizeof buf - 1); if (len > 0) { buf[len] = '\0'; char *colon = strchr(buf, ':'); if (colon) { *colon++ = '\0'; unsigned long addr = inet_addr(buf); if (addr != (unsigned long) -1) { char *after = nsnull; pid_t pid = strtol(colon, &after, 0); if (pid != 0 && *after == '\0') { if (addr != inaddr.s_addr) { // Remote lock: give up even if stuck. break; } // kill(pid,0) is a neat trick to check if a // process exists if (kill(pid, 0) == 0 || errno != ESRCH) { // Local process appears to be alive, ass-u-me it // is another Mozilla instance, or a compatible // derivative, that's currently using the profile. // XXX need an "are you Mozilla?" protocol break; } } } } } // Lock seems to be bogus: try to claim it. Give up after a large // number of attempts (100 comes from the 4.x codebase). (void) unlink(oldFileName); if (++tries > 100) break; } PR_smprintf_free(signature); signature = nsnull; if (symlink_rv == 0) { // We exclusively created the symlink: record its name for eventual // unlock-via-unlink. mPidLockFileName = strdup(oldFileName); if (mPidLockFileName) { PR_APPEND_LINK(this, &mPidLockList); if (!setupPidLockCleanup++) { // Clean up on normal termination. atexit(RemovePidLockFiles); // Clean up on abnormal termination, using POSIX sigaction. // Don't arm a handler if the signal is being ignored, e.g., // because mozilla is run via nohup. struct sigaction act, oldact; #ifdef HAVE_SIGINFO_T act.sa_sigaction = FatalSignalHandler; act.sa_flags = SA_SIGINFO; #else act.sa_handler = FatalSignalHandler; act.sa_flags = 0; #endif sigfillset(&act.sa_mask); #ifdef HAVE_SIGINFO_T #define CATCH_SIGNAL(signame) \ PR_BEGIN_MACRO \ if (sigaction(signame, NULL, &oldact) == 0 && \ ((oldact.sa_flags & SA_SIGINFO) ? \ (oldact.sa_sigaction != (my_sigaction_t) SIG_IGN) : \ (oldact.sa_handler != SIG_IGN))) \ { \ sigaction(signame, &act, &signame##_oldact); \ } \ PR_END_MACRO #else #define CATCH_SIGNAL(signame) \ PR_BEGIN_MACRO \ if (sigaction(signame, NULL, &oldact) == 0 && \ (oldact.sa_handler != SIG_IGN)) \ { \ sigaction(signame, &act, &signame##_oldact); \ } \ PR_END_MACRO #endif CATCH_SIGNAL(SIGHUP); CATCH_SIGNAL(SIGINT); CATCH_SIGNAL(SIGQUIT); CATCH_SIGNAL(SIGILL); CATCH_SIGNAL(SIGABRT); CATCH_SIGNAL(SIGSEGV); CATCH_SIGNAL(SIGTERM); #undef CATCH_SIGNAL } } } else if (symlink_errno != EEXIST) #endif /* USE_SYMLINK_LOCKING */ { // Symlinks aren't supported (for example, on Win32 SAMBA servers). // F_SETLK is not well supported on all NFS servers, which is why we // try symlinks first. nsCAutoString filePath; rv = lockFile->GetNativePath(filePath); if (NS_FAILED(rv)) return rv; mLockFileDesc = open(filePath.get(), O_WRONLY | O_CREAT | O_TRUNC, 0666); if (mLockFileDesc == -1) { NS_ERROR("Failed to open lock file."); return NS_ERROR_FAILURE; } struct flock lock; lock.l_start = 0; lock.l_len = 0; // len = 0 means entire file lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; if (fcntl(mLockFileDesc, F_SETLK, &lock) == -1) { if (errno == EAGAIN || errno == EACCES) return NS_ERROR_FILE_ACCESS_DENIED; return NS_ERROR_FAILURE; } } #ifdef USE_SYMLINK_LOCKING else { // Couldn't create the symlink (but symlink(2) is supported). // This error code will cause the right dialog to be displayed. return NS_ERROR_FILE_ACCESS_DENIED; } #endif /* USE_SYMLINK_LOCKING */ #endif /* XP_UNIX */ mHaveLock = PR_TRUE; return rv; } nsresult nsProfileLock::Unlock() { nsresult rv = NS_OK; if (mHaveLock) { #if defined (XP_MAC) if (mLockFile) rv = mLockFile->Remove(PR_FALSE); #if TARGET_CARBON if (mLockFileDesc != -1) { bsd_close(mLockFileDesc); mLockFileDesc = -1; } #endif #elif defined (XP_WIN) if (mLockFileHandle != INVALID_HANDLE_VALUE) { CloseHandle(mLockFileHandle); mLockFileHandle = INVALID_HANDLE_VALUE; } #elif defined (XP_OS2) if (mLockFileHandle != -1) { DosClose(mLockFileHandle); mLockFileHandle = -1; } #elif defined (XP_UNIX) if (mPidLockFileName) { PR_REMOVE_LINK(this); (void) unlink(mPidLockFileName); free(mPidLockFileName); mPidLockFileName = nsnull; } else if (mLockFileDesc != -1) { close(mLockFileDesc); mLockFileDesc = -1; // Don't remove it } #endif mHaveLock = PR_FALSE; } return rv; }