Is there a Windows API for converting File ID and Volume Serial to a unique path? - c++

Background: I know that in Windows, two volumes with the same serial cannot be mounted at the same time. Moreover, there cannot exist two files within a single mounted volume with the same file ID. EDIT: this may not be the case. Then, given any volume handle or drive letter, the file_id maps to a unique file path. Is there a Windows API that can do this for any volume?
Progress: Here's how far I got:
#import <Windows.h>
...
FILE_ID_DESCRIPTOR file_id;
file_id.dwSize = 24;
file_id.Type = FileIdType;
file_id.QuadPart = /* file reference number */;
...
BOOL File::open(HANDLE *volume) {
file = OpenFileById(*volume, &file_id, SYNCHRONIZE | FILE_READ_ATTRIBUTES
| READ_CONTROL | ACCESS_SYSTEM_SECURITY,
SHARE_MODE, NULL, OPEN_FLAGS);
if (file == INVALID_HANDLE_VALUE) {
if (GetLastError() == 1314UL) { /* SACL permissions denied */
file = OpenFileById(*volume, &file_id, SYNCHRONIZE | FILE_READ_ATTRIBUTES
| READ_CONTROL, SHARE_MODE, NULL, OPEN_FLAGS);
perms = 0x4 | 0x2 | 0x1;
}
if (GetLastError() == 5UL) { /* Security permissions denied */
file = OpenFileById(*volume, &file_id, SYNCHRONIZE | FILE_READ_ATTRIBUTES,
SHARE_MODE, NULL, OPEN_FLAGS);
perms = 0x2 | 0x1;
}
if (file == INVALID_HANDLE_VALUE) { /* All permissions denied */
file = OpenFileById(*volume, &file_id, SYNCHRONIZE,
SHARE_MODE, NULL, OPEN_FLAGS);
perms = 0x1;
}
if (file == INVALID_HANDLE_VALUE) /* File name denied|file doesn't exist */
perms = 0;
} else {
perms = 0x8 | 0x4 | 0x2 | 0x1;
}
return file != INVALID_HANDLE_VALUE;
}
...
if (perms) {
path = new WCHAR[260]; /* expected file length */
l = GetFinalPathNameByHandle(file, path, 260, 0);
if (l >= 260) {
delete[] path;
path = new WCHAR[l];
GetFinalPathNameByHandle(file, path, l, 0);
} else if (l <= 0) {
delete[] path;
path = NULL;
}
}
In the first part of the code, I open a handle to the file, using an already-open handle to the volume, which is easy to do given the mounted drive letter. file is a handle to the file I am about to open.
In the second part, I use the handle to acquire the full name including DOS-style drive name (for example, C:\Users\rmanne\Downloads\test.txt). As long as perms is non-zero, I am guaranteed a human-readable absolute path in the second part of the code. However, I have only tested this with NTFS. According to the API, it will work with CsvFS and ReFS as well, but I haven't had a chance to test it with these yet.
The two functions are provided here:
http://msdn.microsoft.com/en-us/library/windows/desktop/aa364962%28v=vs.85%29.aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/aa365432%28v=vs.85%29.aspx
As you can see, GetFinalPathNameByHandle is well-defined for all filesystems that Windows can mount and read without extra drivers or work-arounds. However, OpenFileByFileId is only defined for NTFS, ReFS, and CsvFS. I do not know whether or not it is defined for FAT or exFAT drives. However, it will, by specification, not work with network shared drives (SMB).
I have the option to just cache the file name because of the way I traverse all of the files, but caching these is incredibly memory-inefficient, taking up to 500MB of memory, whereas, not caching takes about 100MB of memory. Is there a different api that would enable me to get the name of a file given the file_id?

Related

Enumerate all partitions and test if they are NTFS

I'm using:
DWORD d = GetLogicalDrives();
for (int i = 0; i < 26; i++)
{
if ((1 << i) & d) // drive letter 'A' + i present on computer
{
wstring s = std::wstring(L"\\\\.\\") + wchar_t('A' + i) + L":";
PARTITION_INFORMATION diskInfo;
DWORD dwResult;
HANDLE dev = CreateFile(LPWSTR(s.c_str()), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO, NULL, 0, &diskInfo, sizeof(diskInfo), &dwResult, NULL);
CloseHandle(dev);
if (diskInfo.PartitionType == PARTITION_IFS)
{
...
}
}
}
to enumerate all NTFS partitions of a computer.
It works on my Windows 7, on a Windows 8.1 I tried it on, and on a Windows 10 computer.
But it fails on another Windows 10 computer: on this one, the volume C: has a diskInfo.PartitionType value equal to 0x00, instead of 0x07 (PARTITION_IFS).
This value is (see the doc here):
PARTITION_ENTRY_UNUSED : 0x00 : An unused entry partition.
This is strange, since, I can confirm, the partition is really NTFS.
Questions:
Is it well-known that IOCTL_DISK_GET_PARTITION_INFO is not 100% reliable to get the partition type?
What would be a more reliable way to enumerate all NTFS volumes?
Note: I also looked at using IOCTL_DISK_GET_PARTITION_INFO_EX instead of IOCTL_DISK_GET_PARTITION_INFO but then the structure PARTITION_INFORMATION_EX does not seem to give informations about PartitionType, whereas the structure PARTITION_INFORMATION does give access to PartitionType.
As #RemyLebeau says, you are not checking the return value for each call.
PARTITION_ENTRY_UNUSED often means the DeviceIoControl() call failed. It depends on the permissions of your user. You should check your user's access rights to see if it has the FILE_READ_DATA permission (included in GENERIC_READ) on volume C:. In my test environment, if you have no access to open volume C: with GENERIC_READ, CreateFile() returns INVALID_HANDLE_VALUE, and then DeviceIoControl() fails as well.
EDIT:
I suggest using GetVolumeInformation(), for example:
wchar_t fs[MAX_PATH + 1] = { 0 };
GetVolumeInformationW(L"C:\\", NULL, 0, NULL, NULL, NULL, fs, MAX_PATH + 1);
And you will see the Type info in the fs buffer.
I did further investigation thanks to #RemyLebeau's comments with:
HANDLE dev = CreateFile(..., GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL);
if (dev == INVALID_HANDLE_VALUE)
{
DWORD err = GetLastError(); // then MessageBox
}
else
{
BOOL ret = DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO, NULL, 0, &diskInfo, sizeof(diskInfo), &dwResult, NULL);
if (ret == FALSE)
{
DWORD err = GetLastError(); // then MessageBox
}
CloseHandle(dev);
}
on the computer where it failed (computer with Windows 10). I found that CreateFile succeeded but then DeviceIoControl failed with GetLastError being 1 i.e. ERROR_INVALID_FUNCTION (see System Error Codes (0-499)).
Conclusion (I quote Remy's comment):
That means IOCTL_DISK_GET_PARTITION_INFO is not supported by the device you passed to DeviceIoControl().
I then tried with IOCTL_DISK_GET_PARTITION_INFO_EX:
PARTITION_INFORMATION_EX diskInfo;
BOOL ret = DeviceIoControl(dev, IOCTL_DISK_GET_PARTITION_INFO_EX, NULL, 0, &diskInfo, sizeof(diskInfo), &lpBytesReturned, NULL);
and then it worked. I could see that diskInfo.PartitionStyle was PARTITION_STYLE_GPT (=1), and this was the reason why IOCTL_DISK_GET_PARTITION_INFO failed. I quote Remy's comment again:
IOCTL_DISK_GET_PARTITION_INFO is not supported on GPT partitioned drives.
So here's the conclusion:
use IOCTL_DISK_GET_PARTITION_INFO_EX instead of IOCTL_DISK_GET_PARTITION_INFO
if diskInfo.PartitionStyle is 0 (PARTITION_STYLE_MBR) then diskInfo.Mbr.PartitionType can be tested. If it's 0x07, it's NTFS.
if diskInfo.PartitionStyle is 1 (PARTITION_STYLE_GPT) then diskInfo.Gpt.PartitionType can be tested, see here: https://learn.microsoft.com/en-us/windows/desktop/api/winioctl/ns-winioctl-_partition_information_gpt. Even if the NTFS Wikipedia page mentions the GUID EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 for NTFS in the GPT case, this GUID is in fact unrelated to file system (see comment below about this).
it's probably easier to use GetVolumeInformation() instead and just compare if the result is the "NTFS" string, as in the other answer
in my particular case, I initially wanted to test if a volume is NTFS or not before attempting an indexing with DeviceIoControl(hVol, FSCTL_ENUM_USN_DATA, ...) because I thought such MFT querying would be limited to NTFS volumes. In fact, an easier solution would be to NOT TEST if it's NTFS or not, and just do the FSCTL_ENUM_USN_DATA. The worst that can happen is that FSCTL_ENUM_USN_DATA fails with ERROR_INVALID_FUNCTION error, per the documentation:
"ERROR_INVALID_FUNCTION The file system on the specified volume does not support this control code."

FindFirstFile returns INVALID_HANDLE_VALUE when lpFileName is volume

There is a short example:
WIN32_FIND_DATA fd;
HANDLE h = FindFirstFile(L"C:", &fd);
if (h == INVALID_HANDLE_VALUE)
{
wprintf(L"Err = %d\n", GetLastError());
return 1;
}
do {
std::wstring fullPath(fd.cFileName);
wprintf(L"FullPath = %s\n", fullPath.c_str());
} while (FindNextFile(h, &fd));
FindClose(h);
I'm confused since
HANDLE h = FindFirstFile(L"C:", &fd); // OK
HANDLE h = FindFirstFile(L"E:", &fd); // INVALID_HANDLE_VALUE
HANDLE h = FindFirstFile(L"F:", &fd); // INVALID_HANDLE_VALUE
But E and F are real, existing volumes.
I do so because I need all information from the WIN32_FIND_DATA structure which will be passed to the kernel mode.
This question seems to be based on a misunderstanding. You say:
I do not want to examine the files and directories in "E:*", I want to get information about this volume.
and
I need all information from the WIN32_FIND_DATA structure.
Well, the find data information is meaningful for file and directory objects, but not for volume objects.
You'll need to use some other means to obtain volume information. Perhaps GetVolumeInformation, WMI, or maybe something else depending on your needs.
I think you have to put "E:\\*" not only the volume. Or you want to find the volume entry? Also, it is good to clean fd structure memset( &fd, 0, sizeof fd ).
On my machine (Win7 x64) FindFirstFile(L"C:", &fd) returns -1 (C: is system disc), GetLastError() is 2 (i.e. "file not found").
I've checked how FindFirstFile is working. It converts winapi like path to nt path at some stage using RtlDosPathNameToRelativeNtPathName_U and then checks whether PartName is empty or not.
If it's empty it returns mentioned error code.
if it's a mask, it continues and enumerates entries that match: that's why FindFirstFile(L"C:\\*.*", &fd) returns valid handle. Thus if you need to enumerate all files in C: using form C:\\*.*.
In case if you need a volume information use special function GetVolumeInformation.

C++ using 7zip.dll

I'm developing an app which will need to work with different types of archives. As many of the archive types as possible is good. I have choosen a 7zip.dll as an engine of archive-worker. But there is a problem, does anyone knows how to uncompress a file from archive to memory buffer? As I see, 7zip.dll supports only uncompressing to hard disk. Also, it would be nice to load archive from memory buffer. Has anyone tried to do something like that?
Not sure if I completely understand your needs (for example, don't you need the decompressed file on disk?).
I was looking at LZMA SDK 9.20 and its lzma.txt readme file, and there are plenty of hints that decompression to memory is possible - you may just need to use the C API rather than the C++ interface. Check out, for example, the section called Single-call Decompressing:
When to use: RAM->RAM decompressing
Compile files: LzmaDec.h + LzmaDec.c + Types.h
Compile defines: no defines
Memory Requirements:
- Input buffer: compressed size
- Output buffer: uncompressed size
- LZMA Internal Structures: state_size (16 KB for default settings)
Also, there is this function:
SRes LzmaDec_DecodeToBuf(CLzmaDec *p, Byte *dest, SizeT *destLen,
const Byte *src, SizeT *srcLen, ELzmaFinishMode finishMode, ELzmaStatus *status);
You can utilize these by memory-mapping the archive file. To the best of my knowledge, if your process creates a memory-mapped file with exclusive access (so no other process can access it) and does no explicit flushing, all changes to the file will be kept in memory until the mapping is destroyed or the file closed. Alternatively, you could just load the archive contents in memory.
For the sake of completeness, I hacked together several examples into a demo of using memory mapping in Windows.
#include <stdio.h>
#include <time.h>
#include <Windows.h>
#include <WinNT.h>
// This demo will limit the file to 4KiB
#define FILE_SIZE_MAX_LOWER_DW 4096
#define FILE_SIZE_MAX_UPPER_DW 0
#define MAP_OFFSET_LOWER_DW 0
#define MAP_OFFSET_UPPER_DW 0
#define TEST_ITERATIONS 1000
#define INT16_SIZE 2
typedef short int int16;
// NOTE: This will not work for Windows less than XP or 2003 Server!
int main()
{
HANDLE hFile, hFileMapping;
PBYTE mapViewStartAddress;
// Note: with no explicit security attributes, the process needs to have
// the necessary rights (e.g. read, write) to this location.
LPCSTR path = "C:\\Users\\mcmlxxxvi\\Desktop\\test.dat";
// First, open a file handle.
hFile = CreateFile(path,
GENERIC_READ | GENERIC_WRITE, // The file is created with Read/Write permissions
FILE_SHARE_READ, // Set this to 0 for exclusive access
NULL, // Optional security attributes
CREATE_ALWAYS, // File is created if not found, overwritten otherwise
FILE_ATTRIBUTE_TEMPORARY, // This affects the caching behaviour
0); // Attributes template, can be left NULL
if ((hFile) == INVALID_HANDLE_VALUE)
{
fprintf(stderr, "Unable to open file");
return 1;
}
// Then, create a memory mapping for the opened file.
hFileMapping = CreateFileMapping(hFile, // Handle for an opened file
NULL, // Optional security attributes
PAGE_READWRITE, // File can be mapped for Read/Write access
FILE_SIZE_MAX_UPPER_DW, // Maximum file size split in DWORDs.
FILE_SIZE_MAX_LOWER_DW, // NOTE: I may have these two mixed up!
NULL); // Optional name
if (hFileMapping == 0)
{
CloseHandle(hFile);
fprintf(stderr, "Unable to open file for mapping.");
return 1;
}
// Next, map a view (a continuous portion of the file) to a memory region
// The view must start and end at an offset that is a multiple of
// the allocation granularity (roughly speaking, the machine page size).
mapViewStartAddress = (PBYTE)MapViewOfFile(hFileMapping, // Handle to a memory-mapped file
FILE_MAP_READ | FILE_MAP_WRITE, // Maps the view for Read/Write access
MAP_OFFSET_UPPER_DW, // Offset in the file from which
MAP_OFFSET_LOWER_DW, // the view starts, split in DWORDs.
FILE_SIZE_MAX_LOWER_DW); // Size of the view (here, entire file)
if (mapViewStartAddress == 0)
{
CloseHandle(hFileMapping);
CloseHandle(hFile);
fprintf(stderr, "Couldn't map a view of the file.");
return 1;
}
// This is where actual business stuff belongs.
// This example application does iterations of reading and writing
// random numbers for the entire length of the file.
int16 value;
errno_t result = 0;
srand((int)time(NULL));
for (int i = 0; i < TEST_ITERATIONS; i++)
{
// Write
for (int j = 0; j < FILE_SIZE_MAX_LOWER_DW / INT16_SIZE; j++)
{
value = rand();
result = memcpy_s(mapViewStartAddress + j * INT16_SIZE, INT16_SIZE, &value, INT16_SIZE);
if (result != 0)
{
CloseHandle(hFileMapping);
CloseHandle(hFile);
fprintf(stderr, "File write error during iteration #%d, error %d", i, GetLastError());
return 1;
}
}
// Read
SetFilePointer(hFileMapping, 0, 0, FILE_BEGIN);
for (int j = 0; j < FILE_SIZE_MAX_LOWER_DW / sizeof(int); j++)
{
result = memcpy_s(&value, INT16_SIZE, mapViewStartAddress + j * INT16_SIZE, INT16_SIZE);
if (result != 0)
{
CloseHandle(hFileMapping);
CloseHandle(hFile);
fprintf(stderr, "File read error during iteration #%d, error %d", i, GetLastError());
return 1;
}
}
}
// End business stuff
CloseHandle(hFileMapping);
CloseHandle(hFile);
return 0;
}

How Do you set MOTW on an Executable

How do you set MOTW (Mark of the Web) on an executable that is downloaded from the internet?
This data is stored in an NTFS alternative file stream alongside an executable. The stream is called Zone.Identifier:
Windows® Internet Explorer® uses the stream name Zone.Identifier for storage of URL security zones.
The fully qualified form is sample.txt:Zone.Identifier:$DATA
The stream is a simple text stream of the form:
[ZoneTransfer]
ZoneId=3
MSDN-SECZONES gives an explanation of security zones.
(N.B. The original has a space between the colon and "Zone" but I think this is erroneous.)
You can find the ZoneIds in UrlMon.h in the SDK; there's an enum which equates to
enum URLZONE {
URLZONE_LOCAL_MACHINE = 0,
URLZONE_INTRANET = 1,
URLZONE_TRUSTED = 2,
URLZONE_INTERNET = 3,
URLZONE_RESTRICTED = 4
};
(The original uses previous value + 1 rather than absolute values.)
As Hans says in the comments, these can be written with the standard Win32 file APIs CreateFile and WriteFile.
Firefox always writes Internet Zone, zone 3 - Firefox code here (MPL/LGPL/GPL tri-license):
bool SetInternetZoneIdentifier(const FilePath& full_path) {
const DWORD kShare = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE;
std::wstring path = full_path.value() + L":Zone.Identifier";
HANDLE file = CreateFile(path.c_str(), GENERIC_WRITE, kShare, NULL,
OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (INVALID_HANDLE_VALUE == file)
return false;
const char kIdentifier[] = "[ZoneTransfer]\nZoneId=3";
DWORD written = 0;
BOOL result = WriteFile(file, kIdentifier, arraysize(kIdentifier), &written,
NULL);
CloseHandle(file);
if (!result || written != arraysize(kIdentifier)) {
DCHECK(FALSE);
return false;
}
return true;
}
Alternatively there's an IE COM API CLSID_PersistentZoneIdentifier you can use to abstract this all for you.
It is not explicitly stated in RFC 3514, but today, due to increased security requirements, implementations should really retain the information of the presence or absence of the RFC3514 bit in a network transmission, when they write files out to disk, and vice-versa for reading from disk.

Why am I having problems recursively deleting directories?

I've written an application that uses the WIN32 api to create a temporarily directory hierarchy. Now, when wanting to delete the directories when shutting down the application I'm running into some problems.
So lets say I have a directory hierarchy: C:\temp\directory\subdirectory\
I'm using this recursive function:
bool Dir::deleteDirectory(std::string& directoryname, int flags)
{
if(directoryname.at(directoryname.size()-1) != '\\') directoryname += '\\';
if ((flags & CONTENTS) == CONTENTS)
{
WIN32_FIND_DATAA fdata;
HANDLE dhandle;
directoryname += "\\*";
dhandle = FindFirstFileA(directoryname.c_str(), &fdata);
// Loop through all the files in the main directory and delete files & make a list of directories
while(true)
{
if(FindNextFileA(dhandle, &fdata))
{
std::string filename = fdata.cFileName;
if(filename.compare("..") != 0)
{
std::string filelocation = directoryname.substr(0, directoryname.size()-2) + StringManip::reverseSlashes(filename);
// If we've encountered a directory then recall this function for that specific folder.
if(!isDirectory(filelocation)) DeleteFileA(filename.c_str());
else deleteDirectory(filelocation, DIRECTORY_AND_CONTENTS);
}
} else if(GetLastError() == ERROR_NO_MORE_FILES) break;
}
directoryname = directoryname.substr(0, directoryname.size()-2);
}
if ((flags & DIRECTORY) == DIRECTORY)
{
HANDLE DirectoryHandle;
DirectoryHandle = CreateFileA(directoryname.c_str(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
bool DeletionResult = (RemoveDirectoryA(directoryname.c_str()) != 0)?true:false;
CloseHandle(DirectoryHandle);
return DeletionResult;
}
return true;
}
This function iterates over the directory contents of the temp directory; and for each directory in the temp directory it keeps recalling itself until it's at the lowest directory; subdirectory in the example.
There are also 3 flags defined
enum DirectoryDeletion
{
CONTENTS = 0x1,
DIRECTORY = 0x2,
DIRECTORY_AND_CONTENTS = (0x1 | 0x2)
};
When using this function, it only removes the lowest subdirectory and I can't remove the ones higher in hierarchy because it says that the directory is not empty. When I go and look to the directory 'subdirectory' is only removed after the application ends. However, when I try to encapsulate this in a non recursive simple main application I have no problems at all with deleting the directories.
There's a Windows API, SHFileOperation, that will do a recursive folder delete for you.
LONG DeleteDirectoryAndAllSubfolders(LPCWSTR wzDirectory)
{
WCHAR szDir[MAX_PATH+1]; // +1 for the double null terminate
SHFILEOPSTRUCTW fos = {0};
StringCchCopy(szDir, MAX_PATH, wzDirectory);
int len = lstrlenW(szDir);
szDir[len+1] = 0; // double null terminate for SHFileOperation
// delete the folder and everything inside
fos.wFunc = FO_DELETE;
fos.pFrom = szDir;
fos.fFlags = FOF_NO_UI;
return SHFileOperation( &fos );
}
You're not closing dhandle from all those FindFirstFile calls, so each directory has a reference to it when you try to delete it.
And, why do you need to create DirectoryHandle? It's not needed, and will probably also block the directory deletion.
When your app closes, those handles are forced close, and (I guess) the last attempted delete then succeeds.
SHFileOperations works great on Windows 7. In fact in the IFileOperation documentation says
IFileOperation can only be applied in a single-threaded apartment (STA) situation. It cannot be used for a multithreaded apartment (MTA) situation. For MTA, you still must use SHFileOperation.
However my issue with SHFileOperations is it doesn't seem to support paths longer than 260 characters, and does not support \?\ prefix for long filenames.
This is a real pain....but a recursive function is still needed if you want ability to handle paths longer than 260 characters (Which NTFS supports - but not Windows Explorer, command prompt commands etc)
Well, I found several bugs in this code.. here is what I found
bool Dir::deleteDirectory(std::string& directoryname, int flags)
{
if(directoryname.at(directoryname.size()-1) != '\\') directoryname += '\\';
if ((flags & CONTENTS) == CONTENTS)
{
WIN32_FIND_DATAA fdata;
HANDLE dhandle;
//BUG 1: Adding a extra \ to the directory name..
directoryname += "*";
dhandle = FindFirstFileA(directoryname.c_str(), &fdata);
//BUG 2: Not checking for invalid file handle return from FindFirstFileA
if( dhandle != INVALID_HANDLE_VALUE )
{
// Loop through all the files in the main directory and delete files & make a list of directories
while(true)
{
if(FindNextFileA(dhandle, &fdata))
{
std::string filename = fdata.cFileName;
if(filename.compare("..") != 0)
{
//BUG 3: caused by BUG 1 - Removing too many characters from string.. removing 1 instead of 2
std::string filelocation = directoryname.substr(0, directoryname.size()-1) + filename;
// If we've encountered a directory then recall this function for that specific folder.
//BUG 4: not really a bug, but spurious function call - we know its a directory from FindData already, use it.
if( (fdata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0)
DeleteFileA(filelocation.c_str());
else
deleteDirectory(filelocation, DIRECTORY_AND_CONTENTS);
}
} else if(GetLastError() == ERROR_NO_MORE_FILES) break;
}
directoryname = directoryname.substr(0, directoryname.size()-2);
//BUG 5: Not closing the FileFind with FindClose - OS keeps handles to directory open. MAIN BUG
FindClose( dhandle );
}
}
if ((flags & DIRECTORY) == DIRECTORY)
{
HANDLE DirectoryHandle;
DirectoryHandle = CreateFileA(directoryname.c_str(),
FILE_LIST_DIRECTORY,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
NULL);
//BUG 6: Not checking CreateFileA for invalid handle return.
if( DirectoryHandle != INVALID_HANDLE_VALUE )
{
bool DeletionResult = (RemoveDirectoryA(directoryname.c_str()) != 0)?true:false;
CloseHandle(DirectoryHandle);
return DeletionResult;
}
else
{
return true;
}
}
return true;
}
Try calling FindClose to close handle returned by FindFileFileA.
I don't see a FindClose for your dhandle. An open handle means that the directory is still in use.
MSDN says: "When the search handle is no longer needed, close it by using the FindClose function, not CloseHandle."
(CloseHandle appears to be correct for your DirectoryHandle further down, but not for the dhandle used in the Find loop.)
The main issue has already been answered, but here's something I noticed. Your main while loop seems a bit fragile to me...
while(true)
{
if(FindNextFileA(dhandle, &fdata))
{
//...
} else if(GetLastError() == ERROR_NO_MORE_FILES) break;
}
This ends if FindNextFile ends because there are no more files in the directory. But what if it ever ends for some other reason? If something abnormal happens, it seems you could end up with an infinite loop.
I'd think if FindNextFile fails for any reason, then you'll want to stop the loop and start returning through the recursive calls. So I'd suggest simply removing the GetLastError test and just making it "else break;"
Actually, after a moment's thought I would probably just reduce it to:
while(FindNextFileA(dhandle, &fdata))
{
//...
}