/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Mozilla Communicator client code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Alec Flett * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsPrefService.h" #include "nsAppDirectoryServiceDefs.h" #include "nsDirectoryServiceDefs.h" #include "nsICategoryManager.h" #include "nsCategoryManagerUtils.h" #include "nsNetUtil.h" #include "nsIFile.h" #include "nsILocalFile.h" #include "nsIObserverService.h" #include "nsPrefBranch.h" #include "nsXPIDLString.h" #include "nsCRT.h" #include "nsCOMArray.h" #include "nsXPCOMCID.h" #include "nsAutoPtr.h" #include "nsQuickSort.h" #include "prmem.h" #include "pldhash.h" #include "prefapi.h" #include "prefread.h" #include "prefapi_private_data.h" #include "nsITimelineService.h" // Definitions #define INITIAL_PREF_FILES 10 // Prototypes static nsresult openPrefFile(nsIFile* aFile); static nsresult pref_InitInitialObjects(void); //----------------------------------------------------------------------------- /* * Constructor/Destructor */ nsPrefService::nsPrefService() : mDontWriteUserPrefs(PR_FALSE) { } nsPrefService::~nsPrefService() { PREF_Cleanup(); } /* * nsISupports Implementation */ NS_IMPL_THREADSAFE_ADDREF(nsPrefService) NS_IMPL_THREADSAFE_RELEASE(nsPrefService) NS_INTERFACE_MAP_BEGIN(nsPrefService) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIPrefService) NS_INTERFACE_MAP_ENTRY(nsIPrefService) NS_INTERFACE_MAP_ENTRY(nsIObserver) NS_INTERFACE_MAP_ENTRY(nsIPrefBranch) NS_INTERFACE_MAP_ENTRY(nsIPrefBranch2) NS_INTERFACE_MAP_ENTRY(nsIPrefBranchInternal) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_END /* * nsIPrefService Implementation */ nsresult nsPrefService::Init() { nsPrefBranch *rootBranch = new nsPrefBranch("", PR_FALSE); if (!rootBranch) return NS_ERROR_OUT_OF_MEMORY; mRootBranch = (nsIPrefBranch2 *)rootBranch; nsXPIDLCString lockFileName; nsresult rv; rv = PREF_Init(); NS_ENSURE_SUCCESS(rv, rv); rv = pref_InitInitialObjects(); NS_ENSURE_SUCCESS(rv, rv); /* * The following is a small hack which will allow us to only load the library * which supports the netscape.cfg file if the preference is defined. We * test for the existence of the pref, set in the all.js (mozilla) or * all-ns.js (netscape 6), and if it exists we startup the pref config * category which will do the rest. */ rv = mRootBranch->GetCharPref("general.config.filename", getter_Copies(lockFileName)); if (NS_SUCCEEDED(rv)) NS_CreateServicesFromCategory("pref-config-startup", static_cast(static_cast(this)), "pref-config-startup"); nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1", &rv); if (observerService) { rv = observerService->AddObserver(this, "profile-before-change", PR_TRUE); if (NS_SUCCEEDED(rv)) { rv = observerService->AddObserver(this, "profile-do-change", PR_TRUE); } } return(rv); } NS_IMETHODIMP nsPrefService::Observe(nsISupports *aSubject, const char *aTopic, const PRUnichar *someData) { nsresult rv = NS_OK; if (!nsCRT::strcmp(aTopic, "profile-before-change")) { if (!nsCRT::strcmp(someData, NS_LITERAL_STRING("shutdown-cleanse").get())) { if (mCurrentFile) { mCurrentFile->Remove(PR_FALSE); mCurrentFile = nsnull; } } else { rv = SavePrefFile(nsnull); } } else if (!nsCRT::strcmp(aTopic, "profile-do-change")) { ResetUserPrefs(); rv = ReadUserPrefs(nsnull); } else if (!nsCRT::strcmp(aTopic, "reload-default-prefs")) { // Reload the default prefs from file. pref_InitInitialObjects(); } return rv; } NS_IMETHODIMP nsPrefService::ReadUserPrefs(nsIFile *aFile) { nsresult rv; if (nsnull == aFile) { rv = UseDefaultPrefFile(); UseUserPrefFile(); NotifyServiceObservers(NS_PREFSERVICE_READ_TOPIC_ID); } else { rv = ReadAndOwnUserPrefFile(aFile); } return rv; } NS_IMETHODIMP nsPrefService::ResetPrefs() { NotifyServiceObservers(NS_PREFSERVICE_RESET_TOPIC_ID); PREF_CleanupPrefs(); nsresult rv = PREF_Init(); NS_ENSURE_SUCCESS(rv, rv); return pref_InitInitialObjects(); } NS_IMETHODIMP nsPrefService::ResetUserPrefs() { PREF_ClearAllUserPrefs(); return NS_OK; } NS_IMETHODIMP nsPrefService::SavePrefFile(nsIFile *aFile) { return SavePrefFileInternal(aFile); } NS_IMETHODIMP nsPrefService::GetBranch(const char *aPrefRoot, nsIPrefBranch **_retval) { nsresult rv; if ((nsnull != aPrefRoot) && (*aPrefRoot != '\0')) { // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think) nsPrefBranch* prefBranch = new nsPrefBranch(aPrefRoot, PR_FALSE); if (!prefBranch) return NS_ERROR_OUT_OF_MEMORY; rv = CallQueryInterface(prefBranch, _retval); } else { // special case caching the default root rv = CallQueryInterface(mRootBranch, _retval); } return rv; } NS_IMETHODIMP nsPrefService::GetDefaultBranch(const char *aPrefRoot, nsIPrefBranch **_retval) { nsresult rv; // TODO: - cache this stuff and allow consumers to share branches (hold weak references I think) nsPrefBranch* prefBranch = new nsPrefBranch(aPrefRoot, PR_TRUE); if (!prefBranch) return NS_ERROR_OUT_OF_MEMORY; rv = CallQueryInterface(prefBranch, _retval); return rv; } nsresult nsPrefService::NotifyServiceObservers(const char *aTopic) { nsresult rv; nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1", &rv); if (NS_FAILED(rv) || !observerService) return rv; nsISupports *subject = (nsISupports *)((nsIPrefService *)this); observerService->NotifyObservers(subject, aTopic, nsnull); return NS_OK; } nsresult nsPrefService::UseDefaultPrefFile() { nsresult rv, rv2; nsCOMPtr aFile; rv = NS_GetSpecialDirectory(NS_APP_PREFS_50_FILE, getter_AddRefs(aFile)); if (NS_SUCCEEDED(rv)) { rv = ReadAndOwnUserPrefFile(aFile); // Most likely cause of failure here is that the file didn't // exist, so save a new one. mUserPrefReadFailed will be // used to catch an error in actually reading the file. if (NS_FAILED(rv)) { rv2 = SavePrefFileInternal(aFile); NS_ASSERTION(NS_SUCCEEDED(rv2), "Failed to save new shared pref file"); } } return rv; } nsresult nsPrefService::UseUserPrefFile() { nsresult rv = NS_OK; nsCOMPtr aFile; nsDependentCString prefsDirProp(NS_APP_PREFS_50_DIR); rv = NS_GetSpecialDirectory(prefsDirProp.get(), getter_AddRefs(aFile)); if (NS_SUCCEEDED(rv) && aFile) { rv = aFile->AppendNative(NS_LITERAL_CSTRING("user.js")); if (NS_SUCCEEDED(rv)) { PRBool exists = PR_FALSE; aFile->Exists(&exists); if (exists) { rv = openPrefFile(aFile); } else { rv = NS_ERROR_FILE_NOT_FOUND; } } } return rv; } nsresult nsPrefService::MakeBackupPrefFile(nsIFile *aFile) { // Example: this copies "prefs.js" to "Invalidprefs.js" in the same directory. nsAutoString newFilename; nsresult rv = aFile->GetLeafName(newFilename); NS_ENSURE_SUCCESS(rv, rv); newFilename.Insert(NS_LITERAL_STRING("Invalid"), 0); rv = aFile->CopyTo(nsnull, newFilename); NS_ENSURE_SUCCESS(rv, rv); return rv; } nsresult nsPrefService::ReadAndOwnUserPrefFile(nsIFile *aFile) { NS_ENSURE_ARG(aFile); if (mCurrentFile == aFile) return NS_OK; mCurrentFile = aFile; nsresult rv = NS_OK; PRBool exists = PR_FALSE; mCurrentFile->Exists(&exists); if (exists) { rv = openPrefFile(mCurrentFile); if (NS_FAILED(rv)) { mDontWriteUserPrefs = NS_FAILED(MakeBackupPrefFile(mCurrentFile)); } } else { rv = NS_ERROR_FILE_NOT_FOUND; } return rv; } nsresult nsPrefService::SavePrefFileInternal(nsIFile *aFile) { if (nsnull == aFile) { // the gDirty flag tells us if we should write to mCurrentFile // we only check this flag when the caller wants to write to the default if (!gDirty) return NS_OK; // It's possible that we never got a prefs file. nsresult rv = NS_OK; if (mCurrentFile) rv = WritePrefFile(mCurrentFile); return rv; } else { return WritePrefFile(aFile); } } nsresult nsPrefService::WritePrefFile(nsIFile* aFile) { const char outHeader[] = "# Mozilla User Preferences" NS_LINEBREAK NS_LINEBREAK "/* Do not edit this file." NS_LINEBREAK " *" NS_LINEBREAK " * If you make changes to this file while the application is running," NS_LINEBREAK " * the changes will be overwritten when the application exits." NS_LINEBREAK " *" NS_LINEBREAK " * To make a manual change to preferences, you can visit the URL about:config" NS_LINEBREAK " * For more information, see http://www.mozilla.org/unix/customizing.html#prefs" NS_LINEBREAK " */" NS_LINEBREAK NS_LINEBREAK; nsCOMPtr outStreamSink; nsCOMPtr outStream; PRUint32 writeAmount; nsresult rv; if (!gHashTable.ops) return NS_ERROR_NOT_INITIALIZED; // Don't save user prefs if there was an error reading them and we failed // to make a backup copy, since all prefs from the error line to the end of // the file would be lost (bug 361102). if (mDontWriteUserPrefs && aFile == mCurrentFile) return NS_OK; // execute a "safe" save by saving through a tempfile rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(outStreamSink), aFile, -1, 0600); if (NS_FAILED(rv)) return rv; rv = NS_NewBufferedOutputStream(getter_AddRefs(outStream), outStreamSink, 4096); if (NS_FAILED(rv)) return rv; char** valueArray = (char **)PR_Calloc(sizeof(char *), gHashTable.entryCount); if (!valueArray) return NS_ERROR_OUT_OF_MEMORY; pref_saveArgs saveArgs; saveArgs.prefArray = valueArray; saveArgs.saveTypes = SAVE_ALL; // get the lines that we're supposed to be writing to the file PL_DHashTableEnumerate(&gHashTable, pref_savePref, &saveArgs); /* Sort the preferences to make a readable file on disk */ NS_QuickSort(valueArray, gHashTable.entryCount, sizeof(char *), pref_CompareStrings, NULL); // write out the file header outStream->Write(outHeader, sizeof(outHeader) - 1, &writeAmount); char** walker = valueArray; for (PRUint32 valueIdx = 0; valueIdx < gHashTable.entryCount; valueIdx++, walker++) { if (*walker) { outStream->Write(*walker, strlen(*walker), &writeAmount); outStream->Write(NS_LINEBREAK, NS_LINEBREAK_LEN, &writeAmount); NS_Free(*walker); } } PR_Free(valueArray); // tell the safe output stream to overwrite the real prefs file // (it'll abort if there were any errors during writing) nsCOMPtr safeStream = do_QueryInterface(outStream); NS_ASSERTION(safeStream, "expected a safe output stream!"); if (safeStream) { rv = safeStream->Finish(); if (NS_FAILED(rv)) { NS_WARNING("failed to save prefs file! possible dataloss"); return rv; } } gDirty = PR_FALSE; return NS_OK; } static nsresult openPrefFile(nsIFile* aFile) { nsCOMPtr inStr; #if MOZ_TIMELINE { nsCAutoString str; aFile->GetNativePath(str); NS_TIMELINE_MARK_FUNCTION1("load pref file", str.get()); } #endif nsresult rv = NS_NewLocalFileInputStream(getter_AddRefs(inStr), aFile); if (NS_FAILED(rv)) return rv; PRInt64 fileSize; rv = aFile->GetFileSize(&fileSize); if (NS_FAILED(rv)) return rv; nsAutoArrayPtr fileBuffer(new char[fileSize]); if (fileBuffer == nsnull) return NS_ERROR_OUT_OF_MEMORY; PrefParseState ps; PREF_InitParseState(&ps, PREF_ReaderCallback, NULL); // Read is not guaranteed to return a buf the size of fileSize, // but usually will. nsresult rv2 = NS_OK; for (;;) { PRUint32 amtRead = 0; rv = inStr->Read((char*)fileBuffer, fileSize, &amtRead); if (NS_FAILED(rv) || amtRead == 0) break; if (!PREF_ParseBuf(&ps, fileBuffer, amtRead)) rv2 = NS_ERROR_FILE_CORRUPTED; } PREF_FinalizeParseState(&ps); return NS_FAILED(rv) ? rv : rv2; } /* * some stuff that gets called from Pref_Init() */ static int pref_CompareFileNames(nsIFile* aFile1, nsIFile* aFile2, void* /*unused*/) { nsCAutoString filename1, filename2; aFile1->GetNativeLeafName(filename1); aFile2->GetNativeLeafName(filename2); return Compare(filename2, filename1); } /** * Load default pref files from a directory. The files in the * directory are sorted reverse-alphabetically; a set of "special file * names" may be specified which are loaded after all the others. */ static nsresult pref_LoadPrefsInDir(nsIFile* aDir, char const *const *aSpecialFiles, PRUint32 aSpecialFilesCount) { nsresult rv, rv2; PRBool hasMoreElements; nsCOMPtr dirIterator; // this may fail in some normal cases, such as embedders who do not use a GRE rv = aDir->GetDirectoryEntries(getter_AddRefs(dirIterator)); if (NS_FAILED(rv)) { // If the directory doesn't exist, then we have no reason to complain. We // loaded everything (and nothing) successfully. if (rv == NS_ERROR_FILE_NOT_FOUND) rv = NS_OK; return rv; } rv = dirIterator->HasMoreElements(&hasMoreElements); NS_ENSURE_SUCCESS(rv, rv); nsCOMArray prefFiles(INITIAL_PREF_FILES); nsCOMArray specialFiles(aSpecialFilesCount); nsCOMPtr prefFile; while (hasMoreElements && NS_SUCCEEDED(rv)) { nsCAutoString leafName; rv = dirIterator->GetNext(getter_AddRefs(prefFile)); if (NS_FAILED(rv)) { break; } prefFile->GetNativeLeafName(leafName); NS_ASSERTION(!leafName.IsEmpty(), "Failure in default prefs: directory enumerator returned empty file?"); // Skip non-js files if (StringEndsWith(leafName, NS_LITERAL_CSTRING(".js"), nsCaseInsensitiveCStringComparator())) { PRBool shouldParse = PR_TRUE; // separate out special files for (PRUint32 i = 0; i < aSpecialFilesCount; ++i) { if (leafName.Equals(nsDependentCString(aSpecialFiles[i]))) { shouldParse = PR_FALSE; // special files should be process in order; we put them into // the array by index; this can make the array sparse specialFiles.ReplaceObjectAt(prefFile, i); } } if (shouldParse) { prefFiles.AppendObject(prefFile); } } rv = dirIterator->HasMoreElements(&hasMoreElements); } if (prefFiles.Count() + specialFiles.Count() == 0) { NS_WARNING("No default pref files found."); if (NS_SUCCEEDED(rv)) { rv = NS_SUCCESS_FILE_DIRECTORY_EMPTY; } return rv; } prefFiles.Sort(pref_CompareFileNames, nsnull); PRUint32 arrayCount = prefFiles.Count(); PRUint32 i; for (i = 0; i < arrayCount; ++i) { rv2 = openPrefFile(prefFiles[i]); if (NS_FAILED(rv2)) { NS_ERROR("Default pref file not parsed successfully."); rv = rv2; } } arrayCount = specialFiles.Count(); for (i = 0; i < arrayCount; ++i) { // this may be a sparse array; test before parsing nsIFile* file = specialFiles[i]; if (file) { rv2 = openPrefFile(file); if (NS_FAILED(rv2)) { NS_ERROR("Special default pref file not parsed successfully."); rv = rv2; } } } return rv; } static nsresult pref_LoadPrefsInDirList(const char *listId) { nsresult rv; nsCOMPtr dirSvc(do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv)); if (NS_FAILED(rv)) return rv; nsCOMPtr dirList; dirSvc->Get(listId, NS_GET_IID(nsISimpleEnumerator), getter_AddRefs(dirList)); if (dirList) { PRBool hasMore; while (NS_SUCCEEDED(dirList->HasMoreElements(&hasMore)) && hasMore) { nsCOMPtr elem; dirList->GetNext(getter_AddRefs(elem)); if (elem) { nsCOMPtr dir = do_QueryInterface(elem); if (dir) { // Do we care if a file provided by this process fails to load? pref_LoadPrefsInDir(dir, nsnull, 0); } } } } return NS_OK; } //---------------------------------------------------------------------------------------- // Initialize default preference JavaScript buffers from // appropriate TEXT resources //---------------------------------------------------------------------------------------- static nsresult pref_InitInitialObjects() { nsCOMPtr aFile; nsCOMPtr defaultPrefDir; nsresult rv; // first we parse the GRE default prefs. This also works if we're not using a GRE, rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(defaultPrefDir)); NS_ENSURE_SUCCESS(rv, rv); rv = defaultPrefDir->AppendNative(NS_LITERAL_CSTRING("greprefs")); NS_ENSURE_SUCCESS(rv, rv); rv = pref_LoadPrefsInDir(defaultPrefDir, nsnull, 0); if (NS_FAILED(rv)) { NS_WARNING("Error parsing GRE default preferences. Is this an old-style embedding app?"); } // now parse the "application" default preferences rv = NS_GetSpecialDirectory(NS_APP_PREF_DEFAULTS_50_DIR, getter_AddRefs(defaultPrefDir)); NS_ENSURE_SUCCESS(rv, rv); /* these pref file names should not be used: we process them after all other application pref files for backwards compatibility */ static const char* specialFiles[] = { #if defined(XP_MAC) || defined(XP_MACOSX) "macprefs.js" #elif defined(XP_WIN) "winpref.js" #elif defined(XP_UNIX) "unix.js" #if defined(VMS) , "openvms.js" #elif defined(_AIX) , "aix.js" #endif #if defined(MOZ_WIDGET_PHOTON) , "photon.js" #endif #elif defined(XP_OS2) "os2pref.js" #elif defined(XP_BEOS) "beos.js" #endif }; rv = pref_LoadPrefsInDir(defaultPrefDir, specialFiles, NS_ARRAY_LENGTH(specialFiles)); if (NS_FAILED(rv)) { NS_WARNING("Error parsing application default preferences."); } rv = pref_LoadPrefsInDirList(NS_APP_PREFS_DEFAULTS_DIR_LIST); NS_ENSURE_SUCCESS(rv, rv); NS_CreateServicesFromCategory(NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, nsnull, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID); nsCOMPtr observerService = do_GetService("@mozilla.org/observer-service;1", &rv); if (NS_FAILED(rv) || !observerService) return rv; observerService->NotifyObservers(nsnull, NS_PREFSERVICE_APPDEFAULTS_TOPIC_ID, nsnull); return pref_LoadPrefsInDirList(NS_EXT_PREFS_DEFAULTS_DIR_LIST); }