/* -*- Mode: C; tab-width: 4; 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, released * March 31, 1998. * * 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): * Daniel Veditz * Douglas Turner * * Alternatively, the contents of this file may be used under the terms of * either of 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 "nscore.h" #include "nsXPIDLString.h" #include "nsInstall.h" // for error codes #include "prmem.h" #include "ScheduledTasks.h" #include "InstallCleanupDefines.h" #include "nsDirectoryService.h" #include "nsDirectoryServiceDefs.h" #include "nsAppDirectoryServiceDefs.h" static nsresult GetPersistentStringFromSpec(nsIFile* inSpec, nsACString &string) { nsresult rv; nsCOMPtr LocalFile = do_QueryInterface(inSpec, &rv); if (NS_SUCCEEDED(rv)) { rv = LocalFile->GetNativePath(string); } else { string.Truncate(); } return rv; } #ifdef _WINDOWS #include #include PRInt32 ReplaceWindowsSystemFile(nsIFile* currentSpec, nsIFile* finalSpec) { PRInt32 err = -1; // Get OS version info DWORD dwVersion = GetVersion(); nsCAutoString final; nsCAutoString current; finalSpec->GetNativePath(final); currentSpec->GetNativePath(current); // Get build numbers for Windows NT or Win32s if (dwVersion > 0x80000000) { // Windows 95 or Win16 // Place an entry in the WININIT.INI file in the Windows directory // to delete finalName and rename currentName to be finalName at reboot int strlen; char Src[_MAX_PATH]; // 8.3 name char Dest[_MAX_PATH]; // 8.3 name strlen = GetShortPathName( (LPCTSTR)current.get(), (LPTSTR)Src, (DWORD)sizeof(Src) ); if ( strlen > 0 ) { current = Src; } strlen = GetShortPathName( (LPCTSTR) final.get(), (LPTSTR) Dest, (DWORD) sizeof(Dest)); if ( strlen > 0 ) { final = Dest; } // NOTE: use OEM filenames! Even though it looks like a Windows // .INI file, WININIT.INI is processed under DOS AnsiToOem( final.BeginWriting(), final.BeginWriting() ); AnsiToOem( current.BeginWriting(), current.BeginWriting() ); if ( WritePrivateProfileString( "Rename", final.get(), current.get(), "WININIT.INI" ) ) err = 0; } else { // Windows NT if ( MoveFileEx(final.get(), current.get(), MOVEFILE_DELAY_UNTIL_REBOOT) ) err = 0; } return err; } #endif nsresult GetRegFilePath(nsACString ®FilePath) { nsresult rv; nsCOMPtr iFileUtilityPath; //Get the program directory nsCOMPtr directoryService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); if (NS_FAILED(rv)) return nsnull; if (nsSoftwareUpdate::GetProgramDirectory()) // In the stub installer { nsCOMPtr tmp; rv = nsSoftwareUpdate::GetProgramDirectory()->Clone(getter_AddRefs(tmp)); if (NS_FAILED(rv) || !tmp) return nsnull; #if defined (XP_MAC) tmp->AppendNative(ESSENTIAL_FILES); #endif iFileUtilityPath = do_QueryInterface(tmp); } else { rv = directoryService->Get(NS_APP_INSTALL_CLEANUP_DIR, NS_GET_IID(nsIFile), getter_AddRefs(iFileUtilityPath)); } if (NS_FAILED(rv) || !iFileUtilityPath) return nsnull; iFileUtilityPath->AppendNative(CLEANUP_REGISTRY); //Yes, we know using GetPath is buggy on the Mac. //When libreg is fixed to accept nsIFiles we'll change this to match. return iFileUtilityPath->GetNativePath(regFilePath); } PRInt32 DeleteFileNowOrSchedule(nsIFile* filename) { PRBool flagExists; PRInt32 result = nsInstall::SUCCESS; filename->Remove(PR_FALSE); filename->Exists(&flagExists); if (flagExists) result = ScheduleFileForDeletion(filename); return result; } PRInt32 ScheduleFileForDeletion(nsIFile *filename) { // could not delete, schedule it for later RKEY newkey; HREG reg; REGERR err; PRInt32 result = nsInstall::UNEXPECTED_ERROR; nsCAutoString path; GetRegFilePath(path); err = NR_RegOpen(NS_CONST_CAST(char*, path.get()), ®); if ( err == REGERR_OK ) { err = NR_RegAddKey(reg,ROOTKEY_PRIVATE,REG_DELETE_LIST_KEY,&newkey); if ( err == REGERR_OK ) { char valname[20]; err = NR_RegGetUniqueName( reg, valname, sizeof(valname) ); if ( err == REGERR_OK ) { nsCAutoString nameowner; nsresult rv = GetPersistentStringFromSpec(filename, nameowner); if ( NS_SUCCEEDED(rv) && !nameowner.IsEmpty() ) { const char *fnamestr = nameowner.get(); err = NR_RegSetEntry( reg, newkey, valname, REGTYPE_ENTRY_BYTES, (void*)fnamestr, strlen(fnamestr)+sizeof('\0')); if ( err == REGERR_OK ) { result = nsInstall::REBOOT_NEEDED; nsSoftwareUpdate::NeedCleanup(); } } } } NR_RegClose(reg); } return result; } PRInt32 ReplaceFileNow(nsIFile* aReplacementFile, nsIFile* aDoomedFile ) { PRBool flagExists, flagRenamedDoomedFileExists, flagIsEqual; nsCOMPtr replacementFile; nsresult rv; // make a clone of aReplacement file so we touch affect callers aReplacementFile->Clone(getter_AddRefs(replacementFile)); // replacement file must exist, doomed file doesn't have to replacementFile->Exists(&flagExists); if ( !flagExists ) return nsInstall::DOES_NOT_EXIST; // don't have to do anything if the files are the same replacementFile->Equals(aDoomedFile, &flagIsEqual); if ( flagIsEqual ) return nsInstall::SUCCESS; PRInt32 result = nsInstall::ACCESS_DENIED; // first try to rename the doomed file out of the way (if it exists) nsCOMPtr renamedDoomedFile; nsCOMPtr tmpLocalFile; aDoomedFile->Clone(getter_AddRefs(renamedDoomedFile)); renamedDoomedFile->Exists(&flagRenamedDoomedFileExists); if ( flagRenamedDoomedFileExists ) { #ifdef XP_MACOSX // If we clone an nsIFile, and move the clone, the FSRef of the *original* // file is not what you would expect - it points to the moved file. This // is despite the fact that the two FSRefs are independent objects. Until // the OS X file impl is changed to not use FSRefs, need to do this (see // bug 200024). nsCOMPtr doomedFileLocal = do_QueryInterface(aDoomedFile); nsCAutoString doomedFilePath; rv = doomedFileLocal->GetNativePath(doomedFilePath); if (NS_FAILED(rv)) return nsInstall::UNEXPECTED_ERROR; #endif tmpLocalFile = do_QueryInterface(renamedDoomedFile, &rv); // Convert to an nsILocalFile //get the doomedLeafname so we can convert its extension to .old nsAutoString doomedLeafname; nsCAutoString uniqueLeafName; tmpLocalFile->GetLeafName(doomedLeafname); // do not RFind on the native charset! UTF8 or Unicode are OK PRInt32 extpos = doomedLeafname.RFindChar('.'); if (extpos != -1) { // We found the extension; doomedLeafname.Truncate(extpos + 1); //strip off the old extension } doomedLeafname.AppendLiteral("old"); //Now reset the doomedLeafname tmpLocalFile->SetLeafName(doomedLeafname); tmpLocalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644); tmpLocalFile->GetNativeLeafName(uniqueLeafName);//this is the new "unique" doomedLeafname rv = aDoomedFile->Clone(getter_AddRefs(renamedDoomedFile));// Reset renamedDoomed file so aDoomedfile // isn't changed during the MoveTo call if (NS_FAILED(rv)) result = nsInstall::UNEXPECTED_ERROR; else { rv = renamedDoomedFile->MoveToNative(nsnull, uniqueLeafName); if (NS_FAILED(rv)) { // MoveToNative() failing is OK. It simply means that the file // was locked in memory and needs to be replaced on browser // shutdown or system reboot. // // Since renamedDoomedFile->MoveToNative() failed, it created a // 0 byte '-old' file that needs to be cleaned up. tmpLocalFile->Remove(PR_FALSE); } else { // The implementation of MoveToNative() on some platforms (osx and win32) resets // the object to the 'moved to' filename. This is incorrect behavior. This // implementation will be fixed in the future. We need to take into account that // fix by setting renamedDoomedFile to the filename that it was moved to above. // See bug 200024. // // renamedDoomedFile needs to be reset because it's used later on in this // function. rv = renamedDoomedFile->SetNativeLeafName(uniqueLeafName); if (NS_FAILED(rv)) result = nsInstall::UNEXPECTED_ERROR; } } #ifdef XP_MACOSX rv = doomedFileLocal->InitWithNativePath(doomedFilePath); if (NS_FAILED(rv)) result = nsInstall::UNEXPECTED_ERROR; #endif if (result == nsInstall::UNEXPECTED_ERROR) return result; } // if aDoomedFile is still in the way, give up and return result. aDoomedFile->Exists(&flagExists); if ( flagExists ) return result; nsCOMPtr parentofDoomedFile; nsCAutoString doomedLeafname; rv = aDoomedFile->GetParent(getter_AddRefs(parentofDoomedFile)); if ( NS_SUCCEEDED(rv) ) rv = aDoomedFile->GetNativeLeafName(doomedLeafname); if ( NS_SUCCEEDED(rv) ) { rv = replacementFile->MoveToNative(parentofDoomedFile, doomedLeafname); // The implementation of MoveToNative() on some platforms (osx and win32) resets // the object to the 'moved to' filename. This is incorrect behavior. This // implementation will be fixed in the future. We need to take into account that // fix by setting replacementFile to the filename that it was moved to above. // See bug 200024. // // However, since replacementFile is a clone of aReplacementFile and is also // not used beyond here, there's no need to set the path+leafname to what // it was MoveToNative()'ed to. } if (NS_SUCCEEDED(rv)) { if (flagRenamedDoomedFileExists) { // we replaced the old file OK, now we have to // get rid of it if it was renamed out of the way result = DeleteFileNowOrSchedule( renamedDoomedFile ); } } else { // couldn't rename file, try to put old file back renamedDoomedFile->MoveToNative(nsnull, doomedLeafname); // No need to reset remanedDoomedFile after a MoveToNative() call // because renamedDoomedFile is not used beyond here. } return result; } PRInt32 ReplaceFileNowOrSchedule(nsIFile* aReplacementFile, nsIFile* aDoomedFile, PRInt32 aMode) { PRInt32 result = ReplaceFileNow( aReplacementFile, aDoomedFile ); if ( result == nsInstall::ACCESS_DENIED ) { // if we couldn't replace the file schedule it for later #ifdef _WINDOWS if ( (aMode & WIN_SYSTEM_FILE) && (ReplaceWindowsSystemFile(aReplacementFile, aDoomedFile) == 0) ) return nsInstall::REBOOT_NEEDED; #endif RKEY listkey; RKEY filekey; HREG reg; REGERR err; nsCAutoString regFilePath; GetRegFilePath(regFilePath); if ( REGERR_OK == NR_RegOpen(NS_CONST_CAST(char*, regFilePath.get()), ®) ) { err = NR_RegAddKey( reg, ROOTKEY_PRIVATE, REG_REPLACE_LIST_KEY, &listkey ); if ( err == REGERR_OK ) { char valname[20]; REGERR err2; err = NR_RegGetUniqueName( reg, valname, sizeof(valname) ); if ( err == REGERR_OK ) { err = NR_RegAddKey( reg, listkey, valname, &filekey ); if ( REGERR_OK == err ) { nsCAutoString srcowner; nsCAutoString destowner; nsresult rv = GetPersistentStringFromSpec(aReplacementFile, srcowner); nsresult rv2 = GetPersistentStringFromSpec(aDoomedFile, destowner); if ( NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2) ) { const char *fsrc = srcowner.get(); const char *fdest = destowner.get(); err = NR_RegSetEntry( reg, filekey, REG_REPLACE_SRCFILE, REGTYPE_ENTRY_BYTES, (void*)fsrc, strlen(fsrc)+sizeof('\0')); err2 = NR_RegSetEntry(reg, filekey, REG_REPLACE_DESTFILE, REGTYPE_ENTRY_BYTES, (void*)fdest, strlen(fdest)+sizeof('\0')); if ( err == REGERR_OK && err2 == REGERR_OK ) { result = nsInstall::REBOOT_NEEDED; nsSoftwareUpdate::NeedCleanup(); } else NR_RegDeleteKey( reg, listkey, valname ); } } } } NR_RegClose(reg); } } return result; } //----------------------------------------------------------------------------- // // STARTUP: DO SCHEDULED ACTIONS // //----------------------------------------------------------------------------- void DeleteScheduledFiles(HREG); void ReplaceScheduledFiles(HREG); void PerformScheduledTasks(HREG reg) { DeleteScheduledFiles( reg ); ReplaceScheduledFiles( reg ); } void DeleteScheduledFiles( HREG reg ) { REGERR err; RKEY key; REGENUM state = 0; nsresult rv = NS_OK; // perform scheduled file deletions if (REGERR_OK == NR_RegGetKey(reg,ROOTKEY_PRIVATE,REG_DELETE_LIST_KEY,&key)) { // the delete key exists, so we loop through its children // and try to delete all the listed files char namebuf[MAXREGNAMELEN]; char valbuf[MAXREGPATHLEN]; nsCOMPtr doomedFile; nsCOMPtr spec; if (NS_SUCCEEDED(rv)) { while (REGERR_OK == NR_RegEnumEntries( reg, key, &state, namebuf, sizeof(namebuf), 0 ) ) { uint32 bufsize = sizeof(valbuf); // gets changed, must reset err = NR_RegGetEntry( reg, key, namebuf, valbuf, &bufsize ); if ( err == REGERR_OK ) { // no need to check return value of // SetPersistentDescriptorString, it's always NS_OK //spec->SetPersistentDescriptorString(valbuf); //nsIFileXXX: Do we still need this instead of InitWithPath? NS_NewNativeLocalFile(nsDependentCString(valbuf), PR_TRUE, getter_AddRefs(spec)); spec->Clone(getter_AddRefs(doomedFile)); if (NS_SUCCEEDED(rv)) { PRBool flagExists; doomedFile->Remove(PR_FALSE); doomedFile->Exists(&flagExists); if ( !flagExists ) { // deletion successful, don't have to retry NR_RegDeleteEntry( reg, key, namebuf ); } } } } // delete list node if empty state = 0; err = NR_RegEnumEntries(reg, key, &state, namebuf, sizeof(namebuf), 0); if ( err == REGERR_NOMORE ) { NR_RegDeleteKey(reg, ROOTKEY_PRIVATE, REG_DELETE_LIST_KEY); } } } } void ReplaceScheduledFiles( HREG reg ) { RKEY key; // replace files if any listed if (REGERR_OK == NR_RegGetKey(reg,ROOTKEY_PRIVATE,REG_REPLACE_LIST_KEY,&key)) { char keyname[MAXREGNAMELEN]; char doomedFile[MAXREGPATHLEN]; char srcFile[MAXREGPATHLEN]; nsCOMPtr doomedSpec; nsCOMPtr srcSpec; nsCOMPtr src; nsCOMPtr dest; nsresult rv1, rv2; uint32 bufsize; REGENUM state = 0; while (REGERR_OK == NR_RegEnumSubkeys( reg, key, &state, keyname, sizeof(keyname), REGENUM_CHILDREN)) { bufsize = sizeof(srcFile); REGERR err1 = NR_RegGetEntry( reg, (RKEY)state, REG_REPLACE_SRCFILE, srcFile, &bufsize); bufsize = sizeof(doomedFile); REGERR err2 = NR_RegGetEntry( reg, (RKEY)state, REG_REPLACE_DESTFILE, doomedFile, &bufsize); if ( err1 == REGERR_OK && err2 == REGERR_OK ) { rv1 = NS_NewNativeLocalFile(nsDependentCString(srcFile), PR_TRUE, getter_AddRefs(src)); rv1 = src->Clone(getter_AddRefs(srcSpec)); rv2 = NS_NewNativeLocalFile(nsDependentCString(doomedFile), PR_TRUE, getter_AddRefs(dest)); rv2 = dest->Clone(getter_AddRefs(doomedSpec)); if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2)) { // finally now try to do the replace PRInt32 result = ReplaceFileNow( srcSpec, doomedSpec ); if ( result == nsInstall::DOES_NOT_EXIST || result == nsInstall::SUCCESS ) { // This one is done NR_RegDeleteKey( reg, key, keyname ); } } } } // delete list node if empty state = 0; if (REGERR_NOMORE == NR_RegEnumSubkeys( reg, key, &state, keyname, sizeof(keyname), REGENUM_CHILDREN )) { NR_RegDeleteKey(reg, ROOTKEY_PRIVATE, REG_REPLACE_LIST_KEY); } } }