Время от времени приходится сталкиваться с проблемой перемещения или удаления файлов, которые используются системой в настоящий момент, будь то исполняемый модуль или 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),
А 9Х - файл 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шника. Единственное нормальное применение этому это, наверно, инсталлятор или функция обновления в самой программе.