How to prevent my function from delayed deleting in RemoveDirectory() WINAPI? - c++

How do I correct the piece of code below for it to work properly? The purpose of the function is only to delete directories and files inside of the directory.
Because when I run this function (even in a separate thread), it doesn't delete the folder passed in the first argument due to some handles existing (probably).
And Windows doesn't delete the folder until the end of the program, see code below.
How do I fix existing of handles before the RemoveDirectory is called?
I already tried to move the HANDLE variable (which is inside of the function below) out of stack when RemoveDirectory is called.
The problem is just in the delayed deleting of the folder (not other files, they delete normally).
int DeleteDirectory(const std::string &refcstrRootDirectory,
bool bDeleteSubdirectories = true)
{
bool bSubdirectory = false; // Flag, indicating whether
// subdirectories have been found
HANDLE hFile; // Handle to directory
std::string strFilePath; // Filepath
std::string strPattern; // Pattern
WIN32_FIND_DATA FileInformation; // File information
strPattern = refcstrRootDirectory + "\\*.*";
hFile = ::FindFirstFile(strPattern.c_str(), &FileInformation);
if(hFile != INVALID_HANDLE_VALUE)
{
do
{
if(FileInformation.cFileName[0] != '.')
{
strFilePath.erase();
strFilePath = refcstrRootDirectory + "\\" + FileInformation.cFileName;
if(FileInformation.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if(bDeleteSubdirectories)
{
// Delete subdirectory
int iRC = DeleteDirectory(strFilePath, bDeleteSubdirectories);
if(iRC)
return iRC;
}
else
bSubdirectory = true;
}
else
{
// Set file attributes
if(::SetFileAttributes(strFilePath.c_str(),
FILE_ATTRIBUTE_NORMAL) == FALSE)
return ::GetLastError();
// Delete file
if(::DeleteFile(strFilePath.c_str()) == FALSE)
return ::GetLastError();
}
}
} while(::FindNextFile(hFile, &FileInformation) == TRUE);
// Close handle
::FindClose(hFile);
DWORD dwError = ::GetLastError();
if(dwError != ERROR_NO_MORE_FILES)
return dwError;
else
{
if(!bSubdirectory)
{
// Set directory attributes
if(::SetFileAttributes(refcstrRootDirectory.c_str(),
FILE_ATTRIBUTE_NORMAL) == FALSE)
return ::GetLastError();
// Delete directory
if(::RemoveDirectory(refcstrRootDirectory.c_str()) == FALSE)
return ::GetLastError();
}
}
}
return 0;
}
p.s. the piece of code was taken from here:
How to delete a folder in C++?

First of all, I apologize for asking such a misleading question but...
I have found my mistake, It is not in the function that I posted in the question.
I have a project where I used my function DirectoryExists, and there was a function from the header "opendir" (It is a portable version of linux header for Windows and there are probably WINAPI HANDLEs inside of it). And I forgot to close the directory after I've opened it to check if it exists.
bool DirectoryExists(const std::string & path)
{
DIR *dir = opendir(path.c_str());
if (dir) {
return true;
}
else
return false;
}
And I've corrected the error:
bool DirectoryExists(const std::string & path)
{
DIR *dir = opendir(path.c_str());
closedir(dir);
if (dir) {
return true;
}
else
return false;
}
I hope that this question can help someone to understand why RemoveDirectory() could work as not expected. There could be some handles that you did not notice and It's not that clear If you're not a professional Windows programmer.

Related

Detect which window has focus in other instance and post a message with a CString parameter to it

I have code that executes in InitInstance that checks if my application is already running, and if so brings it to the foreground. Standard code:
if (m_hMutex != nullptr)
{ // indicates running instance
if (::GetLastError() == ERROR_ALREADY_EXISTS)
{
EnumWindows(searcher, (LPARAM)&hOther);
if (hOther != nullptr)
{
::SetForegroundWindow(::GetLastActivePopup(hOther));
if (IsIconic(hOther))
{
::ShowWindow(hOther, SW_RESTORE);
}
}
return FALSE; // terminates the creation
}
}
The above I have no issues with. Recently I added support for double-clicking a file from File Explorer and it open in my software CDialog derived). So I do the following in InitInstance:
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
if (PathFileExists(cmdInfo.m_strFileName))
{
m_bOpenFileFromFileExplorer = true;
m_strFileToOpenFromFileExplorerPath = cmdInfo.m_strFileName;
}
Then, in my main dialog OnInitDialog I do this:
if (theApp.OpenFileFromFileExplorer())
{
CString strFileToOpen = theApp.GetFileToOpenFromFileExplorerPath();
CString strFileExtension = PathFindExtension(strFileToOpen);
strFileExtension.MakeLower();
if (strFileExtension == _T(".srr"))
PostMessage(WM_COMMAND, ID_FILE_OPENREPORT);
else if (strFileExtension == _T(".mwb"))
PostMessage(WM_COMMAND, ID_FILE_OPEN_CHRISTIAN_LIFE_AND_MINISTRY_REPORT);
}
Finally, each of my respective handlers for each editor does something like this):
void CMeetingScheduleAssistantDlg::OnFileOpenReport()
{
CCreateReportDlg dlgReport(this);
CString strFilePath, strFileName;
if (theApp.OpenFileFromFileExplorer())
{
strFilePath = theApp.GetFileToOpenFromFileExplorerPath();
strFileName = PathFindFileName(strFilePath);
if (strFilePath == _T("") || strFileName == _T(""))
{
// Error!
return;
}
}
else
{
CString strTitle, strFilter;
strTitle.LoadString(IDS_STR_SELECT_SRR_FILE);
strFilter.LoadString(IDS_STR_SRR_FILTER);
CFileDialog dlgOpen(TRUE, _T(".SRR"),
nullptr, OFN_PATHMUSTEXIST | OFN_HIDEREADONLY, strFilter, this);
dlgOpen.m_ofn.lpstrTitle = strTitle;
// get a file to open from user
if (dlgOpen.DoModal() != IDOK)
return;
strFilePath = dlgOpen.GetPathName();
strFileName = dlgOpen.GetFileName();
}
// AJT V9.1.0 - Most Recent File List support
theApp.AddToRecentFileList(strFilePath);
// tell report we want to open it
dlgReport.SetFileToOpen(strFilePath, strFileName);
// display it
dlgReport.DoModal();
// AJT V9.1.0 Bug Fix
SetDayStates(m_calStart);
SetDayStates(m_calEnd);
}
The other handler works in a similar way. There are no issues with the code implemented and users can double-click a file and it will open in the software with the right editor.
Ofcourse, if my software is already running (but only on the primary dialog) and the user double clicks a file, the duplicate instance will trigger and it will simply bring that window to the foreground.
What I would like to do is this:
Is the duplicate instance on the primary window?
Bring it to the foreground.
Trigger to open this file the user has double in this attempted instance.
Shutdown this instance.
Else
Bring it to the foreground.
Not much else we can do since a modal window is open in the other instance.
So just shut down.
End if
So how do I do that bit:
Is the duplicate instance on the primary window?
Bring it to the foreground.
Trigger to open this file the user has double in this attempted instance.
Shutdown this instance.
Else
?
Update
The problem is that:
HWND hOther = nullptr;
if (DetectRunningInstance(hOther))
{
DetectFileToOpenFromFileExplorer(); // AJT v20.1.6
CString strFile = GetFileToOpenFromFileExplorerPath();
LPCTSTR lpszString = strFile.GetBufferSetLength(_MAX_PATH);
COPYDATASTRUCT cds;
cds.dwData = 1;
cds.cbData = _MAX_PATH;
cds.lpData = (LPVOID)lpszString;
DWORD dwResult;
SendMessageTimeout(hOther, WM_COPYDATA,
NULL, (LPARAM)(LPVOID)&cds, SMTO_BLOCK, 2000, &dwResult);
strFile.ReleaseBuffer();
return FALSE; // Terminates the creation
}
...
...
BOOL CMeetingScheduleAssistantDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
if (pCopyDataStruct->dwData == nnn)
{
LPCTSTR lpszString = (LPCTSTR)(pCopyDataStruct->lpData);
AfxMessageBox(lpszString);
}
return TRUE;
}
The above passes the string, even if the other instance of MSA has a modal window up. So SendMessageTimeout never actual times out.
Got!
if (GetLastActivePopup() != this)
This is what I have so far...
Once I have determined that another instance is already running, and that a file was provided on the command line, I then run this method before I cancel the duplicate instance:
void CMeetingScheduleAssistantApp::TryToOpenFileInOtherInstance(HWND hOtherInstance)
{
CString strFile = GetFileToOpenFromFileExplorerPath();
LPCTSTR lpszString = strFile.GetBufferSetLength(_MAX_PATH);
COPYDATASTRUCT cds;
cds.dwData = xxxxx;
cds.cbData = _MAX_PATH;
cds.lpData = (LPVOID)lpszString;
DWORD_PTR dwResult;
if (SendMessageTimeout(hOtherInstance, WM_COPYDATA,
NULL, (LPARAM)(LPVOID)&cds, SMTO_BLOCK, 2000, &dwResult) != 0)
{
// The message was sent and processed
if (dwResult == FALSE)
{
// The other instance returned FALSE. This is probably because it
// has a pop-up window open so can't open the file
::OutputDebugString(_T("InitInstance::SendMessageTimeout [dwResult was FALSE].\n"));
}
}
else
{
DWORD dwError = ::GetLastError();
if (dwError == ERROR_TIMEOUT)
{
// The message timed out for some reason
::OutputDebugString(_T("InitInstance::SendMessageTimeout [ERROR_TIMEOUT].\n"));
}
else
{
// Another unknown error
}
CString strError = _T("");
strError.Format(_T("InitInstance::SendMessageTimeout [%d: %s]\n"), dwError, GetLastErrorAsStringEx(dwError));
::OutputDebugString(strError);
}
strFile.ReleaseBuffer();
}
In the WM_COPYDATA message handler:
BOOL CMeetingScheduleAssistantDlg::OnCopyData(CWnd* pWnd, COPYDATASTRUCT* pCopyDataStruct)
{
if (pCopyDataStruct->dwData == xxx)
{
LPCTSTR lpszString = (LPCTSTR)(pCopyDataStruct->lpData);
{
if (GetLastActivePopup() != this) // Popup windows!
{
// TODO: Tell user?
return FALSE;
}
theApp.SetFileToOpenFromFileExplorer(lpszString);
OpenFileFromFileExplorer();
}
}
return TRUE;
}
I was trying to adapt the CopyData approach as described here but could not get it to work — Got it to work and code is shown here.

CHtmlView::Navigate2 and locked files

I am finding that if I navigate to a file and then I try to delete the file the system tells me it is in use.
How can I stopped the files from being "in use" so I can delete it and recreate it to update the html display?
If I create a NEW XML data file every time and navigate to that then I get no problems. This is because there is no file to delete.
But the moment I use the same file I get the file in use issue.
I added code in my dialog OnDestroy method and added an array of temporary files that i create. Then I try to delete them:
for (auto i = 0; i < m_aryStrTempFiles.GetCount(); i++)
{
if (PathFileExists(m_aryStrTempFiles[i]))
{
if (!::DeleteFile(m_aryStrTempFiles[i]))
{
AfxMessageBox(theApp.GetLastErrorAsString(), MB_OK | MB_ICONERROR);
}
}
}
I find that ALL of the files are considered still in use.
The code that creates the temporary file names is not the issue:
CString CMeetingScheduleAssistantApp::GetFolderTempFilenameEx(CString strFolder, CString strToken, CString strSuffix /*_T("htm")*/)
{
CString strFile;
int i;
::GetTempFileName(strFolder, strToken, 0, strFile.GetBuffer(_MAX_PATH));
strFile.ReleaseBuffer();
// Because we will rename to .HTM we must delete old file
::DeleteFile(strFile);
// I can't be sure the suffix is .tmp so I manually
// replace the suffix, whatever it is, with .htm"
i = strFile.ReverseFind(_T('.'));
strFile = strFile.Left(i + 1);
strFile += strSuffix;
return strFile;
}
And this is the code that saves my XML files:
bool CMeetingScheduleAssistantApp::SaveToXML(CString strFileXML, tinyxml2::XMLDocument& rDocXML)
{
FILE *fStream = nullptr;
CString strError, strErrorCode;
errno_t eResult;
bool bDisplayError = false;
int iErrorNo = -1;
using namespace tinyxml2;
// Does the file already exist?
if (PathFileExists(strFileXML))
{
// It does, so try to delete it
if (!::DeleteFile(strFileXML))
{
// Unable to delete!
AfxMessageBox(theApp.GetLastErrorAsString(), MB_OK | MB_ICONINFORMATION);
return false;
}
}
// Now try to create a FILE buffer (allows UNICODE filenames)
eResult = _tfopen_s(&fStream, strFileXML, _T("w"));
if (eResult != 0 || fStream == nullptr) // Error
{
bDisplayError = true;
_tcserror_s(strErrorCode.GetBufferSetLength(_MAX_PATH), _MAX_PATH, errno);
strErrorCode.ReleaseBuffer();
}
else // Success
{
// Now try to save the XML file
XMLError eXML = rDocXML.SaveFile(fStream);
int fileCloseResult = fclose(fStream);
if (eXML != XMLError::XML_SUCCESS)
{
// Error saving
bDisplayError = true;
strErrorCode = rDocXML.ErrorName();
iErrorNo = rDocXML.GetErrorLineNum();
}
if (!bDisplayError)
{
if (fileCloseResult != 0)
{
// There was a problem closing the stream. We should tell the user
bDisplayError = true;
_tcserror_s(strErrorCode.GetBufferSetLength(_MAX_PATH), _MAX_PATH, errno);
strErrorCode.ReleaseBuffer();
}
}
}
if (bDisplayError)
{
if (iErrorNo == -1)
iErrorNo = errno;
strError.Format(IDS_TPL_ERROR_SAVE_XML, strFileXML, strErrorCode, iErrorNo);
AfxMessageBox(strError, MB_OK | MB_ICONINFORMATION);
return false;
}
return true;
}
As you can see, they all close the stream. Yet, even though in OnDestroy I delete the html view first the temporary files still can't be deleted. Why?
The issue was how I was testing for a file still being open:
bool CMeetingScheduleAssistantApp::WaitForFileToBeReady(CString strFile)
{
HANDLE hFile;
int delay = 10;
while ((hFile = CreateFile(strFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL)) == INVALID_HANDLE_VALUE)
{
if (GetLastError() == ERROR_SHARING_VIOLATION) {
Sleep(delay);
if (delay < 5120) // max delay approx 5.Sec
delay *= 2;
}
else
{
AfxMessageBox(theApp.GetLastErrorAsString(), MB_OK | MB_ICONINFORMATION);
return false; // some other error occurred
}
}
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
return true;
}
I was missing the CloseHandle lines of code.

How to check for a tag in an XML File without using DOM in C / C++ (Windows)?

I want to read the XML files in the system32\Tasks folder and check if the tags exist in a file or not.
I only need to check for a tag in the XML file.
And the file doesn't necessarily have to be in the same directory as the project file.
void listdirs(WCHAR *dir, WCHAR *mask)
{
WCHAR fspec1[1000], fname[1000];
WIN32_FIND_DATAW dta = {0};
HANDLE hDta = NULL;
DWORD dLastError = 0;
LPCWSTR fspec = NULL;
swprintf(fspec1, 100, L"%ws%ws", dir, mask);
fspec = reinterpret_cast<LPCWSTR>(fspec1);
if ((hDta = FindFirstFileW(fspec, &dta)) == INVALID_HANDLE_VALUE) {
dLastError = GetLastError();
printf("The error : %s\n", strerror(dLastError));
}
else {
do {
if ((dta.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
{
if(wcscmp(dta.cFileName, L".") !=0 && wcscmp(dta.cFileName, L"..") != 0)
{
//Insert code snippet here to check whether the file has <Date> tag
or not
swprintf(fname, 100, L"%ws%ws%ws", dir, dta.cFileName, L"\\");
listdirs(fname, mask);
}
}
else
{
count++;
printf("%ws\n", dta.cFileName);
printf("Count is : %d\n", count);
}
} while (FindNextFileW(hDta, &dta));
FindClose(hDta);
}
}
I tried using TinyXml but I'm having a hard time understanding it as I have no knowledge of DOM's and would prefer an easier alternative

find list of files in a directory programmatically C++ MFC

I'm trying to find list of files in a directory programmatically and I've written the following code
CStringArray CCL2ProjectDirectoryPage::GetAllFilesNames()
{
WIN32_FIND_DATA fileData;
memset(&fileData, 0, sizeof(WIN32_FIND_DATA));
HANDLE handle = FindFirstFile("d:\\test\\*", &fileData);
CStringArray strArray;
while(handle != INVALID_HANDLE_VALUE)
{
strArray.Add(fileData.cFileName); // the problem is that the fileData.cFileName always contains "."
if(FALSE == FindNextFile(handle, &fileData))
break;
}
FindClose(handle);
return strArray;
}
The problem is that the fileData.cFileName always contains ".".
"." with the first file, ".." with the second file and so on.
what is wrong with this code?
Thanks in advance.
Your code uses just Win32 API to traverse directory/folder. The MFC way of doing this is much simpler. The framework comes with CFileFind which is much easier to use. Also you can not return CStringArray as it does not have copy constructor. You should be using CStringArray reference as out param of your method
void CCL2ProjectDirectoryPage::GetAllFilesNames(CStringArray& files)
{
CFileFind finder;
// start working for files
BOOL bWorking = finder.FindFile(_T("d:\\test\\*"));
while (bWorking)
{
bWorking = finder.FindNextFile();
// skip . and .. files
if (!finder.IsDots())
{
files.Add(finder.GetFileName());
}
}
}
You want this:
CStringArray GetAllFilesNames()
{
WIN32_FIND_DATA fileData;
memset(&fileData, 0, sizeof(WIN32_FIND_DATA));
HANDLE handle = FindFirstFile("d:\\test\\*", &fileData);
CStringArray strArray;
if (handle != INVALID_HANDLE_VALUE)
{
do
{
if (_tcscmp(fileData.cFileName, _T(".")) != 0 && // ignore "." and ".."
_tcscmp(fileData.cFileName, _T("..")) != 0)
{
strArray.Add(fileData.cFileName);
}
} while (FindNextFile(handle, &fileData));
FindClose(handle);
}
return strArray;
}
Disclaimer: this is untested and minimal error checking code just for demonstration purposes.

C++ | Windows - Is there a way to find out which process has ownership of the locked file?

What I want to know is it possible to try an open a file (and when it fails because it's opened with another process with sharing off) to figure out which process is using said file?
The reason I am wanting to know this information is because I am making a little application that will "fix" malicious files.
For example, some malicious/adware etc set the file security descriptor so the user can't delete the file, etc. My application just resets the security descriptor allowing the user to regain control.
I have also seen a file open up its child process with for example (CreateFile) and have Shared Mode turned off so the file can't be touched, then the application would execute the childprocess from memory.
Yes, you can in general just use the openfiles command, after having enabled collection of this information via, it appears, openfiles /local on.
In Windows NT up to and including (it seems) Windows XP there was a similar Resource Kit command named oh, short for open handles.
An alternative to both is to use SysInternal's Process Explorer.
Note: In some cases openfiles will fail to list some handle. This happens for me when Windows refuses to unmount an USB disk, claiming that some process is using a file on that disk. No such process ever shows up.
I have developed a function to locate such process, kill it and delete the locked file.
bool ForceDeleteFile(LPWSTR FileName);
Here is the full source code:
bool KillFileProcess(LPWSTR FileName)
{
HANDLE hProcessSnap;
HANDLE hProcess;
PROCESSENTRY32 pe32;
DWORD dwPriorityClass;
bool result = false;
// Take a snapshot of all processes in the system.
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
//printError(TEXT("CreateToolhelp32Snapshot (of processes)"));
return(FALSE);
}
// Set the size of the structure before using it.
pe32.dwSize = sizeof(PROCESSENTRY32);
// Retrieve information about the first process,
// and exit if unsuccessful
if (!Process32First(hProcessSnap, &pe32))
{
//printError(TEXT("Process32First")); // show cause of failure
CloseHandle(hProcessSnap); // clean the snapshot object
return(FALSE);
}
// Now walk the snapshot of processes, and
// display information about each process in turn
do
{
// Retrieve the priority class.
dwPriorityClass = 0;
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pe32.th32ProcessID);
if (hProcess == NULL)
{
//printError(TEXT("OpenProcess"));
}
else
{
dwPriorityClass = GetPriorityClass(hProcess);
if (!dwPriorityClass)
{
//printError(TEXT("GetPriorityClass"));
}
CloseHandle(hProcess);
if (HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pe32.th32ProcessID))
{
WCHAR filename[MAX_PATH] = {};
if (GetModuleFileNameEx(hProcess, NULL, filename, MAX_PATH))
{
if (_wcsicmp((const wchar_t *)FileName, (const wchar_t *)filename) == NULL)
{
if (TerminateProcess(pe32.th32ProcessID, 0))
{
_tprintf(L"Found: Process full killed\nKILLED!\n");
result = true;
}
else
{
_tprintf(L"Found: Process full \nFailed to terminate\n");
DoRun(((CString)L"taskkill /F /IM " + (CString)pe32.szExeFile).GetBuffer());
result = false;
}
}
}
else
{
// handle error
}
CloseHandle(hProcess);
}
}
} while (Process32Next(hProcessSnap, &pe32));
CloseHandle(hProcessSnap);
return(result);
}
bool ForceDeleteFile(LPWSTR FileName)
{
bool result = DeleteFile(FileName);
if (!result)
{
_tprintf(L"Can't delete file. using DeleteFile(). Trying to locate process and kill it\n");
result = KillFileProcess(FileName);
if (!result)
_tprintf(L"Couldn't find the process\n");
else
{
Sleep(1000);
result = DeleteFile(FileName);
if (result)
_tprintf(L"DeleteFile success");
else
_tprintf(L"DeleteFile ============== failed ===============");
}
}
return result;
}
BOOL TerminateProcess(DWORD dwProcessId, UINT uExitCode)
{
DWORD dwDesiredAccess = PROCESS_TERMINATE;
BOOL bInheritHandle = FALSE;
HANDLE hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
if (hProcess == NULL)
return FALSE;
BOOL result = TerminateProcess(hProcess, uExitCode);
CloseHandle(hProcess);
return result;
}