Code:
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0600
#include <windows.h>
#include <shlwapi.h>
#include <winioctl.h>
#include <cstdio>
#include <cstring>
#include <map>
#include <string>
struct FileSizeInfo
{
ULONGLONG fileSize;
std::wstring name;
};
struct ObjId
{
BYTE id[16];
ObjId(BYTE theId[16])
{
memcpy(id, theId, ARRAYSIZE(id));
}
bool operator<(const ObjId& other) const
{
int ret = memcmp(id, other.id, ARRAYSIZE(id));
return ret < 0;
}
};
typedef std::map<ObjId, FileSizeInfo> FileSizeInfos;
SRWLOCK g_srwLock;
LONG g_outstandingRequests = 0;
DWORD g_totalFiles = 0;
struct ThreadParams
{
std::wstring dir;
FileSizeInfos& fileSizes;
ThreadParams(const std::wstring& dir, FileSizeInfos& fileSizes)
: dir(dir),
fileSizes(fileSizes)
{}
};
DWORD WINAPI UserCallback(PVOID pParams);
void IterateDir(const std::wstring& root, FileSizeInfos& fileSizes)
{
FileSizeInfos localSizeVec;
std::wstring fileSpec = root + L'*';
WIN32_FIND_DATA wfd = {0};
DWORD dirFiles = 0;
HANDLE hDirEntry = FindFirstFile(fileSpec.c_str(), &wfd);
if(hDirEntry == INVALID_HANDLE_VALUE)
{
wprintf(L"Failed to start iterating %s because of error %lu\n", fileSpec.c_str(), GetLastError());
return;
}
do
{
if(wfd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if(wfd.cFileName[0] == L'.' && (wfd.cFileName[1] == 0 || (wfd.cFileName[1] == L'.' && wfd.cFileName[2] == 0)))
{
continue;
}
std::wstring nextRoot = root + wfd.cFileName;
nextRoot += L'\\';
ThreadParams* pTp = new(std::nothrow) ThreadParams(nextRoot, fileSizes);
if(pTp)
{
_InterlockedIncrement(&g_outstandingRequests);
QueueUserWorkItem(&UserCallback, pTp, WT_EXECUTEDEFAULT);
}
else
{
wprintf(L"Failed to allocate memory for threadparams for root %s\n", nextRoot.c_str());
}
}
else
{
++dirFiles;
ULONGLONG fileSize = wfd.nFileSizeHigh;
fileSize <<= 32;
fileSize |= wfd.nFileSizeLow;
std::wstring fullName = root + wfd.cFileName;
FileSizeInfo sizeInfo = {fileSize, fullName};
HANDLE hFile = CreateFile(
fullName.c_str(),
0,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if(hFile == INVALID_HANDLE_VALUE)
{
wprintf(L"Couldn't open handle for %s (%lu)\n", fullName.c_str(), GetLastError());
continue;
}
DWORD dummy = 0;
FILE_OBJECTID_BUFFER idBuffer = {0};
if(DeviceIoControl(hFile, FSCTL_CREATE_OR_GET_OBJECT_ID, NULL, 0, &idBuffer, sizeof(idBuffer), &dummy, NULL))
{
localSizeVec.insert(std::make_pair(ObjId(idBuffer.ObjectId), sizeInfo));
}
CloseHandle(hFile);
}
}
while(FindNextFile(hDirEntry, &wfd));
DWORD err = GetLastError();
FindClose(hDirEntry);
bool ret = (err == ERROR_NO_MORE_FILES);
if(!ret)
{
wprintf(L"Failed to enumerate all files with pattern %s because of error %lu\n", fileSpec.c_str(), err);
}
AcquireSRWLockExclusive(&g_srwLock);
fileSizes.insert(localSizeVec.begin(), localSizeVec.end());
g_totalFiles += dirFiles;
ReleaseSRWLockExclusive(&g_srwLock);
}
DWORD WINAPI UserCallback(PVOID pParams)
{
ThreadParams* pTp = static_cast<ThreadParams*>(pParams);
IterateDir(pTp->dir, pTp->fileSizes);
_InterlockedDecrement(&g_outstandingRequests);
delete pTp;
return 0;
}
int __cdecl wmain(int argc, wchar_t** argv)
{
if(argc < 2)
{
puts("Usage: FileSizeStats <root>");
return 0;
}
InitializeSRWLock(&g_srwLock);
FileSizeInfos fileSizes;
std::wstring rootDir = argv[1];
rootDir += L'\\';
IterateDir(rootDir, fileSizes);
while(_InterlockedCompareExchange(&g_outstandingRequests, 0, 0) != 0)
{
Sleep(1000);
}
ULONGLONG totalSize = 0, biggestFile = 0, smallestFile = UINT64_MAX;
const std::wstring* pBigFile = NULL, *pSmallFile = NULL;
for(FileSizeInfos::const_iterator iter = fileSizes.begin(), end = fileSizes.end();
iter != end;
++iter
)
{
const FileSizeInfo& fi = iter->second;
const ULONGLONG& size = fi.fileSize;
totalSize += size;
if(size < smallestFile)
{
smallestFile = size;
pSmallFile = &fi.name;
}
if(size > biggestFile)
{
biggestFile = size;
pBigFile = &fi.name;
}
}
WCHAR sizeBuf[25] = {0};
StrFormatByteSizeW((LONGLONG)totalSize, sizeBuf, ARRAYSIZE(sizeBuf));
wprintf(L"Total size of %s: %s (%I64u)\n", argv[1], sizeBuf, totalSize);
StrFormatByteSizeW((LONGLONG)biggestFile, sizeBuf, ARRAYSIZE(sizeBuf));
wprintf(L"Biggest file: %s (%s)\n", pBigFile->c_str(), sizeBuf);
StrFormatByteSizeW((LONGLONG)smallestFile, sizeBuf, ARRAYSIZE(sizeBuf));
wprintf(L"Smallest file: %s (%s)\n", pSmallFile->c_str(), sizeBuf);
wprintf(L"Total files %lu\nunique files: %Iu\n", g_totalFiles, fileSizes.size());
return 0;
}