最近在負責資料備份產品的時候遇到一個問題:在Win7系統下,使用者選擇“C:\Users” 作為備份源,發現產品UI上顯示的檔案夾大小和負責備份的Service進程裡的檔案夾大小不一致。
起先,我懷疑是擷取檔案夾大小的方法不對,但是查看代碼之後發現代碼是一樣的。都是枚舉檔案夾和檔案大小,然後累加。後來才發現,UI進程擷取該檔案夾大小的時候,很多目錄不能訪問;而Service進程能訪問該目錄下的所有檔案夾,所以得到的大小比UI上顯示的大很多。這樣確認是由於兩個進程的許可權不一致導致的。
該產品的架構是:UI進程是在目前使用者狀態下啟動並執行,故只有目前使用者的許可權;而後台Service進程是運行在System狀態下的,所以有最高許可權,夢訪問所有目錄。
問題本身:
首先,我們看看Win7下 目錄 C:\Users的內容(把 Hide和System都是設定為可見)
然後在C:\Users\All Users 目錄下,有個“Application Data” 目錄,這個目錄在資源管理員裡是不能訪問。
因為資源管理員實際上也是運行在目前使用者許可權下,所以UI進程取得的目錄大小和通過資源管理員查看屬性的方法看到的目錄大小是一致的。
如何解決問題:
然後我進一步對比這種目錄的屬性,發現“Application Data” 目錄除了具有FILE_ATTRIBUTE_DIRECTORY之外,還有FILE_ATTRIBUTE_SYSTEM、FILE_ATTRIBUTE_REPARSE_POINT以及FILE_ATTRIBUTE_NOT_CONTENT_INDEXED等屬性。但是我們又不能通過屬性來判斷為目前使用者無權訪問,因為有些類似目錄目前使用者是可以訪問的,比如使用者自己的“Application Data”目錄。
所以最完美的方案是:把Service進程的許可權從System降為目前使用者許可權。但是本人查了很多資料,還是不得要領,加上實施風險比較高,所以放棄了。
由於本案例中只要保持兩邊目錄大小顯示一直,所以最後想辦法把這個目錄大小通過UI進程傳給Service進程,而不是讓Service進程自己再擷取一次。
擷取目錄大小的方法:
下面是我們常用的擷取檔案夾大小的函數,僅供參考。
bool GetFolderSize(LPCWSTR szPath, UInt64 *totalSize, UInt64 *totalFiles, UInt64 *totalFolders)
{
if (NULL == szPath)
{
return false;
}
static const int MAX_PATH_LEN = 512;
WCHAR szFileFilter[MAX_PATH_LEN] = {0};
TCHAR szFilePath[MAX_PATH_LEN] = {0};
HANDLE hFind = INVALID_HANDLE_VALUE;
WIN32_FIND_DATA fileinfo;
wcsncpy_s(szFilePath,szPath,MAX_PATH_LEN);
wcscat_s(szFilePath,L"\\");
wcsncpy_s(szFileFilter,szFilePath, MAX_PATH_LEN);
wcscat_s(szFileFilter,L"*.*");
hFind = FindFirstFile(szFileFilter,&fileinfo);
if (INVALID_HANDLE_VALUE == hFind)
{
return false;
}
do
{
if(fileinfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
{
if (!wcscmp(fileinfo.cFileName,L".") || !wcscmp(fileinfo.cFileName,L".."))
{
//Do nothing for "." and ".." folders
}
else
{
WCHAR sztmp[MAX_PATH_LEN];
wcsncpy_s(sztmp,szFilePath,MAX_PATH_LEN);
wcscat_s(sztmp,fileinfo.cFileName);
GetFolderSize(sztmp, totalSize, totalFiles, totalFolders);
if(totalFolders != NULL)
{
++(*totalFolders);
}
}
}
else
{
if(totalFiles != NULL)
{
++(*totalFiles);
}
}
if(totalSize != NULL)
{
(*totalSize) += ((UInt64)MAXDWORD+1) * fileinfo.nFileSizeHigh + fileinfo.nFileSizeLow;
}
}while(FindNextFile(hFind,&fileinfo));
FindClose(hFind);
hFind = INVALID_HANDLE_VALUE;
return true;
}