Шаг 168 - Удаление файлов используемых системой в Windows 98

Время от времени приходится сталкиваться с проблемой перемещения или удаления файлов, которые используются системой в настоящий момент, будь то исполняемый модуль или DLL. Один из таких моментов описан в шаге "Шаг 37 - Война с W32.Nimda.E@mm (dr) virus". Если бы речь шла о Windows NT, то там все предельно просто и, можно сказать, красиво:

MoveFileEx(откуда, куда, MOVEFILE_DELAY_UNTIL_REBOOT)

И все! А хочешь удалить:

MoveFileEx(откуда, NULL, MOVEFILE_DELAY_UNTIL_REBOOT)

Тоже хорошо.

Но беда в том, что эта функция не поддерживается в Windows 9X, ну и ладно сделаем её сами, благо Microsoft оставил нам такую возможность. В Windows 9X используется тот же принцип, что и в NT - на диске сохраняется информация о перемещаемых файлах, которая используется при загрузке системы. Разница в том, что NT использует для этой цели реестр:

(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\PendingFileRenameOperations), 

А - файл WININIT.INI, если вы заглянете в каталог Windows, то скорей всего вы его там не найдете, вместо этого там лежит WININIT.BAK. Если мы заглянем в него, то увидим что-нибудь типа:

[rename]
C:\WINDOWS\SYSTEM\OLEAUT32.DLL=C:\WINDOWS\SYSTEM\OLEAUT32.001
NUL=C:\WINDOWS\SYSTEM\DDHELP.EXE
......

Здесь то, что осталось после инсталляции (у меня от DirectX SDK). Думаю, что детального описания не требуется, в отличие от MoveFileEx, здесь изменено направление - куда=откуда, а вместо NULL - NUL. Одно небольшое замечание: файл WININIT.INI используется в WININIT.EXE при запуске системы, оно не является приложением Win32 и стартует до загрузки защищенного режима диска, поэтому имена файлов (и пути разумеется) должны быть "короткими" (в формате 8.3). Ну а теперь посмотрим что мы с этого имеем. За основу возьмем код Джеффри Рихтера (Jeffry Richter) из Win32 Q&A, выкинем все "лишнее", дадим функции новое имя и снабдим нашими комментариями. Вот её реализация:

BOOL MoveFileOnReboot (LPCTSTR pszExisting, LPCTSTR pszNew) 
{
	BOOL bResult = FALSE;

	//буфер для нашей строки: куда=откуда
	char	szRenameLine[1024];   
	//длина строки; заодно заполним буфер
	int		cchRenameLine = wsprintfA(szRenameLine, 
	#ifdef UNICODE
		"%ls=%ls\r\n", 
	#else      
		"%hs=%hs\r\n", 
	#endif
		(pszNew == NULL) ? __TEXT("NUL") : pszNew, pszExisting); //если NULL, 
						 //то строка будет NUL=откуда

	//запомним как зовется секция в WININIT.INI,
	char szRenameSec[] = "[Rename]\r\n";
	//а длину сосчитаем по ходу
	int cchRenameSec = sizeof(szRenameSec) - 1; 
	
	HANDLE hfile, hfilemap;
	DWORD dwFileSize, dwRenameLinePos;
	TCHAR szPathnameWinInit[_MAX_PATH];
	//Сообразим полный путь к WININIT.INI
	GetWindowsDirectory(szPathnameWinInit, _MAX_PATH);
	lstrcat(szPathnameWinInit, __TEXT("\\WinInit.Ini"));
	// Откроем или создадим WININIT.INI.
	hfile = CreateFile(szPathnameWinInit,      
			GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 
			FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
	if(hfile == INVALID_HANDLE_VALUE)			 
		return bResult;
	//Создаем hfilemap с учетом длины названия нашей секции
	//и нашей строки
	dwFileSize = GetFileSize(hfile, NULL);
	hfilemap = CreateFileMapping(hfile, NULL, PAGE_READWRITE, 0, 
				dwFileSize + cchRenameLine + cchRenameSec, NULL);
	if(hfilemap != NULL) 
	{
		//Проецируем WININIT.INI в память. 
		LPSTR pszWinInit = (LPSTR)MapViewOfFile(hfilemap, 
			FILE_MAP_WRITE, 0, 0, 0);	      
		if(pszWinInit != NULL) 
		{
			// Ищем секцию [Rename] 
			LPSTR pszRenameSecInFile = strstr(pszWinInit, szRenameSec);
			if(pszRenameSecInFile == NULL) 
			{
				//Секции нет - будем добавлять.
				dwFileSize += wsprintfA(&pszWinInit[dwFileSize], "%s",
							szRenameSec);
				//Позиция с которой вставлять
				dwRenameLinePos = dwFileSize;            
			} 
			else 
			{
				//Секция есть
				PSTR pszFirstRenameLine = strchr(pszRenameSecInFile, '\n');
				// Сдвинем содержимое файла на длину нашей строки, 
				// всегда будет добавлятся в начало списка 
				pszFirstRenameLine++;	// 1-й символ новой строки
				memmove(pszFirstRenameLine + cchRenameLine, pszFirstRenameLine, 
					pszWinInit + dwFileSize - pszFirstRenameLine);		            
				// Позиция с которой будем вставлять
				dwRenameLinePos = pszFirstRenameLine - pszWinInit;	         
			}
			// Вставляем строку
			memcpy(&pszWinInit[dwRenameLinePos], szRenameLine,cchRenameLine);
			
			UnmapViewOfFile(pszWinInit);
			// Новая длина файла.
			dwFileSize += cchRenameLine;				
			bResult = TRUE; //...все было просто здорово
		}
		CloseHandle(hfilemap);		
	}
	//Добавляем EOF.
	SetFilePointer(hfile, dwFileSize, NULL, FILE_BEGIN);
	SetEndOfFile(hfile);		
	CloseHandle(hfile);   
	return bResult;
}

В качестве примера небольшой проект: при запуске приложения выводим диалог для ввода пароля и запускаем приложение, если введен неправильный пароль мы тихо удаляем программку. Запустите ее и не забудьте заглянуть в WININIT.INI. Конечно же это не защита, да и сам пример не претендует на изящность, главное - показать, что функция действительно работает. Здесь лишь принцип, и не сам принцип, а его идея. Суть в том, что мы усыпляем бдительность "не хорошего" юзера, даем ему поработать, а после всего уходим не прощаясь (помня о нанесенной обиде :)). Конечно, это подойдет не всегда и не всем, для того чтобы программе удалить себя, можно, к примеру, создав "на лету" batch с бесконечным циклом удаления exeшника. Единственное нормальное применение этому это, наверно, инсталлятор или функция обновления в самой программе.


Загрузить проект | Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Каев Артем - 30.11.2001