Back to index

lightning-sunbird  0.9+nobinonly
ScheduledTasks.cpp
Go to the documentation of this file.
00001 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
00002 /* ***** BEGIN LICENSE BLOCK *****
00003  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
00004  *
00005  * The contents of this file are subject to the Mozilla Public License Version
00006  * 1.1 (the "License"); you may not use this file except in compliance with
00007  * the License. You may obtain a copy of the License at
00008  * http://www.mozilla.org/MPL/
00009  *
00010  * Software distributed under the License is distributed on an "AS IS" basis,
00011  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
00012  * for the specific language governing rights and limitations under the
00013  * License.
00014  *
00015  * The Original Code is Mozilla Communicator client code, released
00016  * March 31, 1998.
00017  *
00018  * The Initial Developer of the Original Code is
00019  * Netscape Communications Corporation.
00020  * Portions created by the Initial Developer are Copyright (C) 1998
00021  * the Initial Developer. All Rights Reserved.
00022  *
00023  * Contributor(s):
00024  *   Daniel Veditz <dveditz@netscape.com>
00025  *   Douglas Turner <dougt@netscape.com>
00026  *
00027  * Alternatively, the contents of this file may be used under the terms of
00028  * either of the GNU General Public License Version 2 or later (the "GPL"),
00029  * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00030  * in which case the provisions of the GPL or the LGPL are applicable instead
00031  * of those above. If you wish to allow use of your version of this file only
00032  * under the terms of either the GPL or the LGPL, and not to allow others to
00033  * use your version of this file under the terms of the MPL, indicate your
00034  * decision by deleting the provisions above and replace them with the notice
00035  * and other provisions required by the GPL or the LGPL. If you do not delete
00036  * the provisions above, a recipient may use your version of this file under
00037  * the terms of any one of the MPL, the GPL or the LGPL.
00038  *
00039  * ***** END LICENSE BLOCK ***** */
00040 
00041 #include "nscore.h"
00042 #include "nsXPIDLString.h"
00043 #include "nsInstall.h" // for error codes
00044 #include "prmem.h"
00045 #include "ScheduledTasks.h"
00046 #include "InstallCleanupDefines.h"
00047 
00048 #include "nsDirectoryService.h"
00049 #include "nsDirectoryServiceDefs.h"
00050 #include "nsAppDirectoryServiceDefs.h"
00051 
00052 static nsresult 
00053 GetPersistentStringFromSpec(nsIFile* inSpec, nsACString &string)
00054 {
00055     nsresult rv;
00056 
00057     nsCOMPtr<nsILocalFile> LocalFile = do_QueryInterface(inSpec, &rv);
00058 
00059     if (NS_SUCCEEDED(rv)) {
00060         rv = LocalFile->GetNativePath(string);
00061     } 
00062     else {
00063         string.Truncate();
00064     }
00065     return rv;
00066 }
00067 
00068 
00069 
00070 
00071 
00072 #ifdef _WINDOWS
00073 #include <sys/stat.h>
00074 #include <windows.h>
00075 
00076 PRInt32 ReplaceWindowsSystemFile(nsIFile* currentSpec, nsIFile* finalSpec)
00077 {
00078     PRInt32 err = -1;
00079 
00080     // Get OS version info 
00081     DWORD dwVersion = GetVersion();
00082 
00083     nsCAutoString final;
00084     nsCAutoString current;
00085 
00086     finalSpec->GetNativePath(final);
00087     currentSpec->GetNativePath(current);
00088  
00089     // Get build numbers for Windows NT or Win32s 
00090 
00091     if (dwVersion > 0x80000000)
00092     {
00093         // Windows 95 or Win16
00094 
00095         // Place an entry in the WININIT.INI file in the Windows directory
00096         // to delete finalName and rename currentName to be finalName at reboot
00097 
00098         int     strlen;
00099         char    Src[_MAX_PATH];   // 8.3 name
00100         char    Dest[_MAX_PATH];  // 8.3 name
00101         
00102         strlen = GetShortPathName( (LPCTSTR)current.get(), (LPTSTR)Src, (DWORD)sizeof(Src) );
00103         if ( strlen > 0 ) 
00104         {
00105             current = Src;
00106         }
00107 
00108         strlen = GetShortPathName( (LPCTSTR) final.get(), (LPTSTR) Dest, (DWORD) sizeof(Dest));
00109         if ( strlen > 0 ) 
00110         {
00111             final = Dest;
00112         }
00113         
00114         // NOTE: use OEM filenames! Even though it looks like a Windows
00115         //       .INI file, WININIT.INI is processed under DOS 
00116         
00117         AnsiToOem( final.BeginWriting(), final.BeginWriting() );
00118         AnsiToOem( current.BeginWriting(), current.BeginWriting() );
00119 
00120         if ( WritePrivateProfileString( "Rename", final.get(), current.get(), "WININIT.INI" ) )
00121             err = 0;
00122     }
00123     else
00124     {
00125        // Windows NT
00126         if ( MoveFileEx(final.get(), current.get(), MOVEFILE_DELAY_UNTIL_REBOOT) )
00127           err = 0;
00128     }
00129     
00130     return err;
00131 }
00132 #endif
00133 
00134 nsresult GetRegFilePath(nsACString &regFilePath)
00135 {
00136     nsresult rv;
00137     nsCOMPtr<nsILocalFile> iFileUtilityPath;
00138     //Get the program directory
00139     nsCOMPtr<nsIProperties> directoryService = 
00140              do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
00141     if (NS_FAILED(rv))
00142         return nsnull;
00143 
00144     if (nsSoftwareUpdate::GetProgramDirectory()) // In the stub installer
00145     {
00146         nsCOMPtr<nsIFile> tmp;
00147         rv = nsSoftwareUpdate::GetProgramDirectory()->Clone(getter_AddRefs(tmp));
00148 
00149         if (NS_FAILED(rv) || !tmp) 
00150             return nsnull;
00151 
00152 #if defined (XP_MAC)
00153         tmp->AppendNative(ESSENTIAL_FILES);
00154 #endif
00155         iFileUtilityPath = do_QueryInterface(tmp);
00156     }
00157     else
00158     {
00159         rv = directoryService->Get(NS_APP_INSTALL_CLEANUP_DIR,
00160                                   NS_GET_IID(nsIFile),
00161                                   getter_AddRefs(iFileUtilityPath));
00162     }
00163     if (NS_FAILED(rv) || !iFileUtilityPath) 
00164         return nsnull;
00165 
00166     iFileUtilityPath->AppendNative(CLEANUP_REGISTRY);
00167 
00168     //Yes, we know using GetPath is buggy on the Mac.
00169     //When libreg is fixed to accept nsIFiles we'll change this to match.
00170     return iFileUtilityPath->GetNativePath(regFilePath);
00171 }
00172 
00173 
00174 PRInt32 DeleteFileNowOrSchedule(nsIFile* filename)
00175 {
00176     PRBool flagExists;  
00177     PRInt32 result = nsInstall::SUCCESS;
00178 
00179     filename->Remove(PR_FALSE);
00180     filename->Exists(&flagExists);
00181     if (flagExists)
00182         result = ScheduleFileForDeletion(filename);
00183  
00184     return result;
00185 } 
00186 
00187 PRInt32 ScheduleFileForDeletion(nsIFile *filename)
00188 {
00189     // could not delete, schedule it for later
00190 
00191     RKEY newkey;
00192     HREG reg;
00193     REGERR  err;
00194     PRInt32 result = nsInstall::UNEXPECTED_ERROR;
00195 
00196     nsCAutoString path;
00197     GetRegFilePath(path);
00198     err = NR_RegOpen(NS_CONST_CAST(char*, path.get()), &reg);
00199 
00200     if ( err == REGERR_OK )
00201     {
00202         err = NR_RegAddKey(reg,ROOTKEY_PRIVATE,REG_DELETE_LIST_KEY,&newkey);
00203         if ( err == REGERR_OK )
00204         {
00205             char    valname[20];
00206 
00207             err = NR_RegGetUniqueName( reg, valname, sizeof(valname) );
00208             if ( err == REGERR_OK )
00209             {
00210                 nsCAutoString nameowner;
00211                 nsresult rv = GetPersistentStringFromSpec(filename, nameowner);
00212                 if ( NS_SUCCEEDED(rv) && !nameowner.IsEmpty() )
00213                 {
00214                     const char *fnamestr = nameowner.get();
00215                     err = NR_RegSetEntry( reg, newkey, valname, 
00216                                           REGTYPE_ENTRY_BYTES, 
00217                                           (void*)fnamestr, 
00218                                           strlen(fnamestr)+sizeof('\0'));
00219 
00220                     if ( err == REGERR_OK )
00221                     {
00222                          result = nsInstall::REBOOT_NEEDED;
00223                          nsSoftwareUpdate::NeedCleanup();
00224                     }
00225                 }
00226             }
00227         }
00228 
00229         NR_RegClose(reg);
00230     }
00231 
00232     return result;
00233 }
00234 
00235 
00236 
00237 
00238 PRInt32 ReplaceFileNow(nsIFile* aReplacementFile, nsIFile* aDoomedFile )
00239 {
00240     PRBool flagExists, flagRenamedDoomedFileExists, flagIsEqual;
00241     nsCOMPtr<nsIFile> replacementFile;
00242     nsresult rv;
00243 
00244     // make a clone of aReplacement file so we touch affect callers
00245     aReplacementFile->Clone(getter_AddRefs(replacementFile));
00246 
00247     // replacement file must exist, doomed file doesn't have to
00248     replacementFile->Exists(&flagExists);
00249     if ( !flagExists )
00250         return nsInstall::DOES_NOT_EXIST;
00251 
00252     // don't have to do anything if the files are the same
00253     replacementFile->Equals(aDoomedFile, &flagIsEqual);
00254     if ( flagIsEqual )
00255         return nsInstall::SUCCESS;
00256 
00257 
00258     PRInt32 result = nsInstall::ACCESS_DENIED;
00259 
00260     // first try to rename the doomed file out of the way (if it exists)
00261     nsCOMPtr<nsIFile>      renamedDoomedFile;
00262     nsCOMPtr<nsILocalFile> tmpLocalFile;
00263     
00264     aDoomedFile->Clone(getter_AddRefs(renamedDoomedFile));
00265     renamedDoomedFile->Exists(&flagRenamedDoomedFileExists);
00266     if ( flagRenamedDoomedFileExists )
00267     {
00268 #ifdef XP_MACOSX
00269         // If we clone an nsIFile, and move the clone, the FSRef of the *original*
00270         // file is not what you would expect - it points to the moved file. This
00271         // is despite the fact that the two FSRefs are independent objects. Until
00272         // the OS X file impl is changed to not use FSRefs, need to do this (see
00273         // bug 200024).
00274         nsCOMPtr<nsILocalFile> doomedFileLocal = do_QueryInterface(aDoomedFile);
00275         nsCAutoString doomedFilePath;
00276         rv = doomedFileLocal->GetNativePath(doomedFilePath);
00277         if (NS_FAILED(rv))
00278             return nsInstall::UNEXPECTED_ERROR;
00279 #endif
00280 
00281         tmpLocalFile = do_QueryInterface(renamedDoomedFile, &rv); // Convert to an nsILocalFile
00282 
00283         //get the doomedLeafname so we can convert its extension to .old
00284         nsAutoString doomedLeafname;
00285         nsCAutoString uniqueLeafName;
00286         tmpLocalFile->GetLeafName(doomedLeafname);
00287 
00288         // do not RFind on the native charset! UTF8 or Unicode are OK
00289         PRInt32 extpos = doomedLeafname.RFindChar('.');
00290         if (extpos != -1)
00291         {
00292             // We found the extension; 
00293             doomedLeafname.Truncate(extpos + 1); //strip off the old extension
00294         }
00295         doomedLeafname.AppendLiteral("old");
00296         
00297         //Now reset the doomedLeafname
00298         tmpLocalFile->SetLeafName(doomedLeafname);
00299         tmpLocalFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0644);
00300         tmpLocalFile->GetNativeLeafName(uniqueLeafName);//this is the new "unique" doomedLeafname
00301 
00302         rv = aDoomedFile->Clone(getter_AddRefs(renamedDoomedFile));// Reset renamedDoomed file so aDoomedfile
00303                                                                    // isn't changed during the MoveTo call
00304         if (NS_FAILED(rv))
00305             result = nsInstall::UNEXPECTED_ERROR;
00306         else
00307         {
00308             rv = renamedDoomedFile->MoveToNative(nsnull, uniqueLeafName);        
00309             if (NS_FAILED(rv))
00310             {
00311                 // MoveToNative() failing is OK.  It simply means that the file
00312                 // was locked in memory and needs to be replaced on browser
00313                 // shutdown or system reboot.
00314                 //
00315                 // Since renamedDoomedFile->MoveToNative() failed, it created a
00316                 // 0 byte '-old' file that needs to be cleaned up.
00317                 tmpLocalFile->Remove(PR_FALSE);
00318             }
00319             else
00320             {
00321                 // The implementation of MoveToNative() on some platforms (osx and win32) resets
00322                 // the object to the 'moved to' filename.  This is incorrect behavior.  This
00323                 // implementation will be fixed in the future.  We need to take into account that
00324                 // fix by setting renamedDoomedFile to the filename that it was moved to above.
00325                 // See bug 200024.
00326                 //
00327                 // renamedDoomedFile needs to be reset because it's used later on in this
00328                 // function.
00329                 rv = renamedDoomedFile->SetNativeLeafName(uniqueLeafName);        
00330                 if (NS_FAILED(rv))
00331                     result = nsInstall::UNEXPECTED_ERROR;
00332             }
00333         }
00334 
00335 #ifdef XP_MACOSX
00336         rv = doomedFileLocal->InitWithNativePath(doomedFilePath);
00337         if (NS_FAILED(rv))
00338             result = nsInstall::UNEXPECTED_ERROR;
00339 #endif
00340 
00341         if (result == nsInstall::UNEXPECTED_ERROR)
00342             return result;
00343     }
00344 
00345 
00346     // if aDoomedFile is still in the way, give up and return result.
00347     aDoomedFile->Exists(&flagExists);
00348     if ( flagExists )
00349         return result;
00350 
00351     nsCOMPtr<nsIFile> parentofDoomedFile;
00352     nsCAutoString doomedLeafname;
00353 
00354     rv = aDoomedFile->GetParent(getter_AddRefs(parentofDoomedFile));
00355     if ( NS_SUCCEEDED(rv) )
00356         rv = aDoomedFile->GetNativeLeafName(doomedLeafname);
00357     if ( NS_SUCCEEDED(rv) )
00358     {
00359         rv = replacementFile->MoveToNative(parentofDoomedFile, doomedLeafname);
00360         // The implementation of MoveToNative() on some platforms (osx and win32) resets
00361         // the object to the 'moved to' filename.  This is incorrect behavior.  This
00362         // implementation will be fixed in the future.  We need to take into account that
00363         // fix by setting replacementFile to the filename that it was moved to above.
00364         // See bug 200024.
00365         //
00366         // However, since replacementFile is a clone of aReplacementFile and is also
00367         // not used beyond here, there's no need to set the path+leafname to what
00368         // it was MoveToNative()'ed to.
00369     }
00370 
00371     if (NS_SUCCEEDED(rv))
00372     {
00373         if (flagRenamedDoomedFileExists)
00374         {
00375             // we replaced the old file OK, now we have to
00376             // get rid of it if it was renamed out of the way
00377             result = DeleteFileNowOrSchedule( renamedDoomedFile );
00378         }
00379     }
00380     else
00381     {
00382         // couldn't rename file, try to put old file back
00383         renamedDoomedFile->MoveToNative(nsnull, doomedLeafname);
00384         // No need to reset remanedDoomedFile after a MoveToNative() call
00385         // because renamedDoomedFile is not used beyond here.
00386     }
00387 
00388     return result;
00389 }
00390 
00391 
00392 
00393 
00394 
00395 PRInt32 ReplaceFileNowOrSchedule(nsIFile* aReplacementFile, nsIFile* aDoomedFile, PRInt32 aMode)
00396 {
00397     PRInt32 result = ReplaceFileNow( aReplacementFile, aDoomedFile );
00398 
00399     if ( result == nsInstall::ACCESS_DENIED )
00400     {
00401         // if we couldn't replace the file schedule it for later
00402 #ifdef _WINDOWS
00403         if ( (aMode & WIN_SYSTEM_FILE) && 
00404              (ReplaceWindowsSystemFile(aReplacementFile, aDoomedFile) == 0) )
00405                 return nsInstall::REBOOT_NEEDED;
00406 #endif
00407 
00408         RKEY    listkey;
00409         RKEY    filekey;
00410         HREG    reg;
00411         REGERR  err;
00412 
00413         nsCAutoString regFilePath;
00414         GetRegFilePath(regFilePath);
00415         if ( REGERR_OK == NR_RegOpen(NS_CONST_CAST(char*, regFilePath.get()), &reg) ) 
00416         {
00417             err = NR_RegAddKey( reg, ROOTKEY_PRIVATE, REG_REPLACE_LIST_KEY, &listkey );
00418             if ( err == REGERR_OK ) 
00419             {
00420                 char     valname[20];
00421                 REGERR   err2;
00422 
00423                 err = NR_RegGetUniqueName( reg, valname, sizeof(valname) );
00424                 if ( err == REGERR_OK )
00425                 {
00426                     err = NR_RegAddKey( reg, listkey, valname, &filekey );
00427                     if ( REGERR_OK == err )
00428                     {
00429                         nsCAutoString srcowner;
00430                         nsCAutoString destowner;
00431                         nsresult rv = GetPersistentStringFromSpec(aReplacementFile, srcowner);
00432                         nsresult rv2 = GetPersistentStringFromSpec(aDoomedFile, destowner);
00433                         if ( NS_SUCCEEDED(rv) && NS_SUCCEEDED(rv2) )
00434                         {
00435                             const char *fsrc  = srcowner.get();
00436                             const char *fdest = destowner.get();
00437                             err = NR_RegSetEntry( reg, filekey, 
00438                                                   REG_REPLACE_SRCFILE,
00439                                                   REGTYPE_ENTRY_BYTES, 
00440                                                   (void*)fsrc, 
00441                                                   strlen(fsrc)+sizeof('\0'));
00442 
00443                             err2 = NR_RegSetEntry(reg, filekey,
00444                                                   REG_REPLACE_DESTFILE,
00445                                                   REGTYPE_ENTRY_BYTES,
00446                                                   (void*)fdest,
00447                                                   strlen(fdest)+sizeof('\0'));
00448 
00449                             if ( err == REGERR_OK && err2 == REGERR_OK )
00450                             {
00451                                 result = nsInstall::REBOOT_NEEDED;
00452                                 nsSoftwareUpdate::NeedCleanup();
00453                             }
00454                             else
00455                                 NR_RegDeleteKey( reg, listkey, valname );
00456                         }
00457                     }
00458                 }
00459             }
00460             NR_RegClose(reg);
00461         }
00462     }
00463 
00464     return result;
00465 }
00466 
00467 
00468 
00469 
00470 //-----------------------------------------------------------------------------
00471 //
00472 //          STARTUP: DO SCHEDULED ACTIONS
00473 //
00474 //-----------------------------------------------------------------------------
00475 
00476 void DeleteScheduledFiles(HREG);
00477 void ReplaceScheduledFiles(HREG);
00478 
00479 void PerformScheduledTasks(HREG reg)
00480 {
00481     DeleteScheduledFiles( reg );
00482     ReplaceScheduledFiles( reg );
00483 }
00484 
00485 
00486 
00487 void DeleteScheduledFiles( HREG reg )
00488 {
00489     REGERR  err;
00490     RKEY    key;
00491     REGENUM state = 0;
00492     nsresult rv = NS_OK;
00493 
00494     // perform scheduled file deletions  
00495     if (REGERR_OK == NR_RegGetKey(reg,ROOTKEY_PRIVATE,REG_DELETE_LIST_KEY,&key))
00496     {
00497         // the delete key exists, so we loop through its children
00498         // and try to delete all the listed files
00499 
00500         char    namebuf[MAXREGNAMELEN];
00501         char    valbuf[MAXREGPATHLEN];
00502 
00503         nsCOMPtr<nsIFile>        doomedFile;
00504         nsCOMPtr<nsILocalFile>   spec;
00505 
00506         if (NS_SUCCEEDED(rv))
00507         {
00508             while (REGERR_OK == NR_RegEnumEntries( reg, key, &state, namebuf,
00509                                                    sizeof(namebuf), 0 ) )
00510             {
00511                 uint32 bufsize = sizeof(valbuf); // gets changed, must reset
00512                 err = NR_RegGetEntry( reg, key, namebuf, valbuf, &bufsize );
00513                 if ( err == REGERR_OK )
00514                 {
00515                     // no need to check return value of 
00516                     // SetPersistentDescriptorString, it's always NS_OK
00517                     //spec->SetPersistentDescriptorString(valbuf);
00518                     //nsIFileXXX: Do we still need this instead of InitWithPath?
00519                     NS_NewNativeLocalFile(nsDependentCString(valbuf), PR_TRUE, getter_AddRefs(spec));
00520                     spec->Clone(getter_AddRefs(doomedFile));
00521                     if (NS_SUCCEEDED(rv)) 
00522                     {
00523                         PRBool flagExists;
00524                         doomedFile->Remove(PR_FALSE);
00525                         doomedFile->Exists(&flagExists);
00526                         if ( !flagExists )
00527                         {
00528                             // deletion successful, don't have to retry
00529                             NR_RegDeleteEntry( reg, key, namebuf );
00530                         }
00531                     }
00532                 }
00533             }
00534 
00535             // delete list node if empty 
00536             state = 0;
00537             err = NR_RegEnumEntries(reg, key, &state, namebuf, sizeof(namebuf), 0);
00538             if ( err == REGERR_NOMORE )
00539             {
00540                 NR_RegDeleteKey(reg, ROOTKEY_PRIVATE, REG_DELETE_LIST_KEY);
00541             }
00542         }
00543     }
00544 }
00545 
00546 
00547 
00548 void ReplaceScheduledFiles( HREG reg )
00549 {
00550     RKEY    key;
00551 
00552     // replace files if any listed 
00553     if (REGERR_OK == NR_RegGetKey(reg,ROOTKEY_PRIVATE,REG_REPLACE_LIST_KEY,&key))
00554     {
00555         char keyname[MAXREGNAMELEN];
00556         char doomedFile[MAXREGPATHLEN];
00557         char srcFile[MAXREGPATHLEN];
00558 
00559         nsCOMPtr<nsIFile>       doomedSpec;
00560         nsCOMPtr<nsIFile>       srcSpec;
00561         nsCOMPtr<nsILocalFile>       src;
00562         nsCOMPtr<nsILocalFile>       dest;
00563         nsresult                rv1, rv2;
00564 
00565         uint32 bufsize;
00566         REGENUM state = 0;
00567         while (REGERR_OK == NR_RegEnumSubkeys( reg, key, &state, 
00568                                keyname, sizeof(keyname), REGENUM_CHILDREN))
00569         {
00570             bufsize = sizeof(srcFile);
00571             REGERR err1 = NR_RegGetEntry( reg, (RKEY)state,
00572                                REG_REPLACE_SRCFILE, srcFile, &bufsize);
00573 
00574             bufsize = sizeof(doomedFile);
00575             REGERR err2 = NR_RegGetEntry( reg, (RKEY)state,
00576                                REG_REPLACE_DESTFILE, doomedFile, &bufsize);
00577 
00578             if ( err1 == REGERR_OK && err2 == REGERR_OK )
00579             {
00580                 rv1 = NS_NewNativeLocalFile(nsDependentCString(srcFile), PR_TRUE, getter_AddRefs(src));
00581                 rv1 = src->Clone(getter_AddRefs(srcSpec));
00582 
00583                 rv2 = NS_NewNativeLocalFile(nsDependentCString(doomedFile), PR_TRUE, getter_AddRefs(dest));
00584                 rv2 = dest->Clone(getter_AddRefs(doomedSpec));
00585 
00586                 if (NS_SUCCEEDED(rv1) && NS_SUCCEEDED(rv2))
00587                 {
00588                     // finally now try to do the replace
00589                     PRInt32 result = ReplaceFileNow( srcSpec, doomedSpec );
00590 
00591                     if ( result == nsInstall::DOES_NOT_EXIST ||
00592                          result == nsInstall::SUCCESS )
00593                     {
00594                         // This one is done
00595                         NR_RegDeleteKey( reg, key, keyname );
00596                     }
00597                 }
00598             }
00599         }
00600 
00601 
00602         // delete list node if empty 
00603         state = 0;
00604         if (REGERR_NOMORE == NR_RegEnumSubkeys( reg, key, &state, keyname,
00605                                      sizeof(keyname), REGENUM_CHILDREN ))
00606         {
00607             NR_RegDeleteKey(reg, ROOTKEY_PRIVATE, REG_REPLACE_LIST_KEY);
00608         }
00609     }
00610 }
00611 
00612