Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

542 lines
20 KiB

  1. // This file contains the implementation of CShellTreeWalker, a COM object
  2. // that inherits IShellTreeWalker, and it will recursively enumerate all the
  3. // files (or directories or both) starting from a root directory that match a
  4. // certain spec.
  5. // 1. The tree walker is reparse point aware, it does not traverse into reparse
  6. // point folders by default, but will if specified
  7. // 2. It keeps track of the number of files, directories, depth, and total
  8. // size of all files encountered.
  9. // 3. It will stop the traversal right away if any error message is returned
  10. // from the callback functions except for E_NOTIMPL
  11. // 4. It will jump out of the current working directory if S_FALSE is returned from callback
  12. // functions.
  13. //
  14. // History:
  15. // 12-5-97 by dli
  16. #include "shellprv.h"
  17. #include "validate.h"
  18. #define IS_FILE_DIRECTORY(pwfd) ((pwfd)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
  19. #define IS_FILE_REPARSE_POINT(pwfd) ((pwfd)->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
  20. // Call back flags for _CallCallBack
  21. #define STWCB_FILE 1
  22. #define STWCB_ERROR 2
  23. #define STWCB_ENTERDIR 3
  24. #define STWCB_LEAVEDIR 4
  25. #define TF_TREEWALKER 0
  26. STDAPI_(DWORD) PathGetClusterSize(LPCTSTR pszPath);
  27. class CShellTreeWalker : public IShellTreeWalker
  28. {
  29. public:
  30. CShellTreeWalker();
  31. // IUnknown
  32. STDMETHODIMP QueryInterface(REFIID riid, void ** ppv);
  33. STDMETHODIMP_(ULONG) AddRef(void) ;
  34. STDMETHODIMP_(ULONG) Release(void);
  35. // IShellTreeWalker
  36. STDMETHODIMP WalkTree(DWORD dwFlags, LPCWSTR pwszWalkRoot, LPCWSTR pwszWalkSpec, int iMaxPath, IShellTreeWalkerCallBack * pstwcb);
  37. private:
  38. LONG _cRef;
  39. DWORD _dwFlags; // Flags indicating the search status
  40. UINT _nMaxDepth; // Maximum depth we walk into
  41. UINT _nDepth; // Current depth
  42. UINT _nFiles; // Number of files we have seen so far
  43. UINT _nDirs; // Number of directories we have seen
  44. BOOL _bFolderFirst; // Do the folders first
  45. DWORD _dwClusterSize; // the size of a cluster
  46. ULONGLONG _ulTotalSize; // total size of all files we have seen
  47. ULONGLONG _ulActualSize; // total size on disk, taking into account compression, sparse files, and cluster slop
  48. TCHAR _szWalkBuf[MAX_PATH]; // The path buffer used in the walk
  49. LPCTSTR _pszWalkSpec; // The spec we use in FindFirstFile and FindNextFile
  50. IShellTreeWalkerCallBack * _pstwcb; // The call back interface pointer
  51. WIN32_FIND_DATA _wfd; // The temp storage of WIN32_FIND_DATA
  52. WIN32_FIND_DATA _fdTopLevelFolder; // The top level folder info
  53. HRESULT _CallCallBacks(DWORD dwCallReason, WIN32_FIND_DATA * pwfd);
  54. HRESULT _ProcessAndRecurse(WIN32_FIND_DATA * pwfd);
  55. HRESULT _TreeWalkerHelper();
  56. BOOL _PathAppend(LPTSTR pszPath, LPCTSTR pszMore); // we have our own PatAppend that dosen't whack pszPath in failure cases
  57. };
  58. CShellTreeWalker::CShellTreeWalker() : _cRef(1)
  59. {
  60. ASSERT(_dwFlags == 0);
  61. ASSERT(_bFolderFirst == FALSE);
  62. ASSERT(_nMaxDepth == 0);
  63. ASSERT(_nDepth == 0);
  64. ASSERT(_nDirs == 0);
  65. ASSERT(_ulTotalSize == 0);
  66. ASSERT(_ulActualSize == 0);
  67. ASSERT(_pszWalkSpec == NULL);
  68. ASSERT(_pstwcb == NULL);
  69. ASSERT(_szWalkBuf[0] == 0);
  70. }
  71. // _CallCallBack: convert the TCHARs to WCHARs and call the callback functions
  72. HRESULT CShellTreeWalker::_CallCallBacks(DWORD dwReason, WIN32_FIND_DATA * pwfd)
  73. {
  74. HRESULT hr;
  75. WCHAR wszDir[MAX_PATH];
  76. WCHAR wszFileName[MAX_PATH];
  77. WIN32_FIND_DATAW wfdw = {0};
  78. WIN32_FIND_DATAW* pwfdw = NULL;
  79. TREEWALKERSTATS tws = {0};
  80. tws.nFiles = _nFiles;
  81. tws.nFolders = _nDirs;
  82. tws.nDepth = _nDepth;
  83. tws.ulTotalSize = _ulTotalSize;
  84. tws.ulActualSize = _ulActualSize;
  85. tws.dwClusterSize = _dwClusterSize;
  86. // _szWalkBuf to wszDir
  87. StringCchCopy(wszDir, ARRAYSIZE(wszDir), _szWalkBuf);
  88. StringCchCopy(wszFileName, ARRAYSIZE(wszFileName), wszDir);
  89. PathCombine(wszFileName, wszFileName, pwfd->cFileName);
  90. if (pwfd && ((dwReason == STWCB_FILE) || (dwReason == STWCB_ENTERDIR)))
  91. {
  92. // WIN32_FIND_DATAA to WIN32_FIND_DATAW
  93. memcpy(&wfdw, pwfd, sizeof(wfdw));
  94. pwfdw = &wfdw;
  95. }
  96. switch (dwReason)
  97. {
  98. case STWCB_FILE:
  99. hr = _pstwcb->FoundFile(wszFileName, &tws, pwfdw);
  100. TraceMsg(TF_TREEWALKER, "TreeWalker Callback FoundFile: %s\\%s dwReason: %x nFiles: %d nDepth: %d nDirs: %d",
  101. _szWalkBuf, pwfd->cFileName, dwReason, _nFiles, _nDepth, _nDirs);
  102. break;
  103. case STWCB_ENTERDIR:
  104. hr = _pstwcb->EnterFolder(wszDir, &tws, pwfdw);
  105. TraceMsg(TF_TREEWALKER, "TreeWalker Callback EnterFolder: %s dwReason: %x nFiles: %d nDepth: %d nDirs: %d",
  106. _szWalkBuf, dwReason, _nFiles, _nDepth, _nDirs);
  107. break;
  108. case STWCB_LEAVEDIR:
  109. hr = _pstwcb->LeaveFolder(wszDir, &tws);
  110. break;
  111. // case STWCB_ERROR:
  112. // hr = _pstwcb->HandleError(S_OK, wszDir, &tws);
  113. // break;
  114. default:
  115. hr = S_OK;
  116. break;
  117. }
  118. // Error messages are significant to us, all E_ messages are interpreted as "Stop right now!!"
  119. if (hr == E_NOTIMPL)
  120. hr = S_OK;
  121. return hr;
  122. }
  123. // Call call back funtions on directories and files, recurse on directories if there is no objection
  124. // from the callback object
  125. HRESULT CShellTreeWalker::_ProcessAndRecurse(WIN32_FIND_DATA * pwfd)
  126. {
  127. HRESULT hr = S_OK;
  128. // Don't recurse on reparse points by default
  129. if (IS_FILE_DIRECTORY(pwfd) && (!IS_FILE_REPARSE_POINT(pwfd) || (_dwFlags & WT_GOINTOREPARSEPOINT)))
  130. {
  131. // NTRAID94635 15mar00: If we are in a symbolic link, we need to detect cycles,
  132. // the common prefix method in BeenThereDoneThat will work as long as we
  133. // keep track of all junction point targets we ran into.
  134. // use _szWalkBuf since we dont want any stack variables, because we are a recursive function
  135. if (_PathAppend(_szWalkBuf, pwfd->cFileName))
  136. {
  137. // We remember the total number of sub directories we have seen
  138. // doesn't matter if the client approves or not(call back returns S_OK or S_FALSE or E_FAIL)
  139. _nDirs++;
  140. // Let the CallBack object know that we are about to enter a directory
  141. if (_dwFlags & WT_NOTIFYFOLDERENTER)
  142. hr = _CallCallBacks(STWCB_ENTERDIR, pwfd);
  143. if ((hr == S_OK) && (_nDepth < _nMaxDepth))
  144. {
  145. _nDepth++;
  146. hr = _TreeWalkerHelper();
  147. _nDepth--;
  148. }
  149. else if (hr == S_FALSE)
  150. hr = S_OK;
  151. // Let the CallBack object know that we are about to leave a directory
  152. if (_dwFlags & WT_NOTIFYFOLDERLEAVE)
  153. _CallCallBacks(STWCB_LEAVEDIR, NULL);
  154. // Peel off the subdirectory we tagged on in the above PathCombine Ex:"c:\bin\fun --> c:\bin"
  155. PathRemoveFileSpec(_szWalkBuf);
  156. }
  157. }
  158. else
  159. {
  160. // use _szWalkBuf since we dont want any stack variables, because we are a recursive function
  161. if (_PathAppend(_szWalkBuf, pwfd->cFileName))
  162. {
  163. // Count the number of files and compute the total size before calling the
  164. // call back object.
  165. ULARGE_INTEGER ulTemp;
  166. _nFiles++;
  167. ulTemp.LowPart = pwfd->nFileSizeLow;
  168. ulTemp.HighPart = pwfd->nFileSizeHigh;
  169. _ulTotalSize += ulTemp.QuadPart;
  170. // when calculating the total size, we need to find out if the file is compressed or sparse (NTFS only case)
  171. if (pwfd->dwFileAttributes & (FILE_ATTRIBUTE_COMPRESSED | FILE_ATTRIBUTE_SPARSE_FILE))
  172. {
  173. // eithe the file is compressed or sparse, we need to call GetCompressedFileSize to get the real
  174. // size on disk for this file (NOTE: GetCompressedFileSize takes into account cluster slop, except
  175. // for files < 1 cluster, and we take care of that below)
  176. ulTemp.LowPart = SHGetCompressedFileSize(_szWalkBuf, &ulTemp.HighPart);
  177. _ulActualSize += ulTemp.QuadPart;
  178. }
  179. else
  180. {
  181. // (reinerf) the cluster size could change if we started on one volume and have now
  182. // walked onto another mounted volume
  183. //
  184. // PathGetClusterSize caches the last request, so this check will be fast in the common case
  185. //
  186. _dwClusterSize = PathGetClusterSize(_szWalkBuf);
  187. // if its not compressed, we just round up to the drive's cluster size. ulTemp was setup
  188. // already for us above, so just round it to the cluster and add it in
  189. _ulActualSize += ROUND_TO_CLUSTER(ulTemp.QuadPart, _dwClusterSize);
  190. }
  191. // Peel off the subdirectory we tagged on in the above PathCombine Ex:"c:\bin\fun --> c:\bin"
  192. PathRemoveFileSpec(_szWalkBuf);
  193. hr = _CallCallBacks(STWCB_FILE, pwfd);
  194. }
  195. }
  196. return hr;
  197. }
  198. #define DELAY_ARRAY_GROW 32
  199. // Recursive function that does the real work on the traversal,
  200. HRESULT CShellTreeWalker::_TreeWalkerHelper()
  201. {
  202. HRESULT hr = S_OK;
  203. TraceMsg(TF_TREEWALKER, "TreeWalkerHelper started on: %s flags: %x nFiles: %d nDepth: %d nDirs: %d",
  204. _szWalkBuf, _dwFlags, _nFiles, _nDepth, _nDirs);
  205. // Let the CallBack object know that we are about to start the walk
  206. // provided he cares about the root
  207. if (_nDepth == 0 && !(_dwFlags & WT_EXCLUDEWALKROOT) &&
  208. (_dwFlags & WT_NOTIFYFOLDERENTER))
  209. {
  210. // Get the info for the TopLevelFolder
  211. HANDLE hTopLevelFolder = FindFirstFile(_szWalkBuf, &_fdTopLevelFolder);
  212. if (hTopLevelFolder == INVALID_HANDLE_VALUE)
  213. {
  214. LPTSTR pszFileName;
  215. DWORD dwAttribs = -1; // assume failure
  216. // We could have failed if we tried to do a FindFirstFile on the root (c:\)
  217. // or if something is really wrong, to test for this we do a GetFileAttributes (GetFileAttributesEx on NT)
  218. // on NT we can use GetFileAttributesEx to get both the attribs and part of the win32fd
  219. if (GetFileAttributesEx(_szWalkBuf, GetFileExInfoStandard, (LPVOID)&_fdTopLevelFolder))
  220. {
  221. // success!
  222. dwAttribs = _fdTopLevelFolder.dwFileAttributes;
  223. pszFileName = PathFindFileName(_szWalkBuf);
  224. StringCchCopy(_fdTopLevelFolder.cFileName, ARRAYSIZE(_fdTopLevelFolder.cFileName), pszFileName);
  225. StringCchCopy(_fdTopLevelFolder.cAlternateFileName, ARRAYSIZE(_fdTopLevelFolder.cAlternateFileName), pszFileName);
  226. }
  227. else
  228. {
  229. // fall back to the ole GetFileAttrbutes
  230. dwAttribs = GetFileAttributes(_szWalkBuf);
  231. if (dwAttribs != -1)
  232. {
  233. // success!
  234. // On win95 we steal a bunch of the find data from our first child, and fake the rest.
  235. // Its the best we can do.
  236. memcpy(&_fdTopLevelFolder, &_wfd, sizeof(_fdTopLevelFolder));
  237. _fdTopLevelFolder.dwFileAttributes = dwAttribs;
  238. pszFileName = PathFindFileName(_szWalkBuf);
  239. StringCchCopy(_fdTopLevelFolder.cFileName, ARRAYSIZE(_fdTopLevelFolder.cFileName), pszFileName);
  240. StringCchCopy(_fdTopLevelFolder.cAlternateFileName, ARRAYSIZE(_fdTopLevelFolder.cAlternateFileName), pszFileName);
  241. }
  242. }
  243. if (dwAttribs == -1)
  244. {
  245. // this is very bad, so we bail
  246. TraceMsg(TF_TREEWALKER, "Tree Walker: GetFileAttributes/Ex(%s) failed. Stopping the walk.", _szWalkBuf);
  247. return E_FAIL;
  248. }
  249. }
  250. else
  251. {
  252. // We sucessfully got the find data, good.
  253. FindClose(hTopLevelFolder);
  254. }
  255. // call the callback for the first enterdir
  256. hr = _CallCallBacks(STWCB_ENTERDIR, &_fdTopLevelFolder);
  257. }
  258. // Do the real tree walk here
  259. if (hr == S_OK)
  260. {
  261. // always use *.* to search when we are not at our maximum level because
  262. // we need the sub directories
  263. // PERF: this can be changed on NT by using FindFirstFileEx if WT_FOLDERONLY
  264. LPCTSTR pszSpec = (_pszWalkSpec && (_nDepth == _nMaxDepth)) ? _pszWalkSpec : c_szStarDotStar;
  265. if (_PathAppend(_szWalkBuf, pszSpec))
  266. {
  267. HDSA hdsaDelayed = NULL; // array of found items that will be delayed and processed later
  268. HANDLE hFind;
  269. // Start finding the sub folders and files
  270. hFind = FindFirstFile(_szWalkBuf, &_wfd);
  271. // Peel off the find spec Ex:"c:\bin\*.* --> c:\bin"
  272. PathRemoveFileSpec(_szWalkBuf);
  273. if (hFind != INVALID_HANDLE_VALUE)
  274. {
  275. BOOL bDir = FALSE;
  276. do
  277. {
  278. // Skip over the . and .. entries.
  279. if (PathIsDotOrDotDot(_wfd.cFileName))
  280. continue;
  281. bDir = BOOLIFY(IS_FILE_DIRECTORY(&_wfd));
  282. // If this is a file, and we are not interested in files or this file spec does not match the one we were
  283. // looking for.
  284. if ((!bDir) && ((_dwFlags & WT_FOLDERONLY) ||
  285. (_pszWalkSpec && (_nDepth < _nMaxDepth) && !PathMatchSpec(_wfd.cFileName, _pszWalkSpec))))
  286. continue;
  287. // The following EQUAL determines whether we want to process
  288. // the data found or save it in the HDSA array and process them later.
  289. // Enumerate the folder or file now? (determined by the WT_FOLDERFIRST flag)
  290. if (bDir == BOOLIFY(_bFolderFirst))
  291. {
  292. // Yes
  293. hr = _ProcessAndRecurse(&_wfd);
  294. // if hr is failure code someone said "stop right now"
  295. // if hr is S_FALSE some one said "quit this directory and start with next one"
  296. if (hr != S_OK)
  297. break;
  298. }
  299. else
  300. {
  301. // No; enumerate it once we're finished with the opposite.
  302. if (!hdsaDelayed)
  303. hdsaDelayed = DSA_Create(sizeof(WIN32_FIND_DATA), DELAY_ARRAY_GROW);
  304. if (!hdsaDelayed)
  305. {
  306. hr = E_OUTOFMEMORY;
  307. break;
  308. }
  309. DSA_AppendItem(hdsaDelayed, &_wfd);
  310. }
  311. } while (FindNextFile(hFind, &_wfd));
  312. FindClose(hFind);
  313. }
  314. else
  315. {
  316. // find first file failed, this is a good place to report error
  317. DWORD dwErr = GetLastError();
  318. TraceMsg(TF_TREEWALKER, "***WARNING***: FindFirstFile faied on %s%s with error = %d", _szWalkBuf, _pszWalkSpec, dwErr);
  319. }
  320. // Process the delayed items, these are either directories or files
  321. if (hdsaDelayed)
  322. {
  323. // we should have finished everything in the above do while loop in folderonly case
  324. ASSERT(!(_dwFlags & WT_FOLDERONLY));
  325. // if hr is failure code someone said "stop right now"
  326. // if hr is S_FALSE some one said "quit this directory and start with next one"
  327. if (hr == S_OK)
  328. {
  329. int ihdsa;
  330. for (ihdsa = 0; ihdsa < DSA_GetItemCount(hdsaDelayed); ihdsa++)
  331. {
  332. WIN32_FIND_DATA * pwfd = (WIN32_FIND_DATA *)DSA_GetItemPtr(hdsaDelayed, ihdsa);
  333. hr = _ProcessAndRecurse(pwfd);
  334. if (hr != S_OK)
  335. break;
  336. }
  337. }
  338. DSA_Destroy(hdsaDelayed);
  339. }
  340. // Let the CallBack object know that we are finishing the walk
  341. if (_nDepth == 0 && !(_dwFlags & WT_EXCLUDEWALKROOT) &&
  342. (_dwFlags & WT_NOTIFYFOLDERLEAVE) && (S_OK == hr))
  343. hr = _CallCallBacks(STWCB_LEAVEDIR, &_fdTopLevelFolder);
  344. // hr was S_FALSE because someone wanted us to jump out of this current directory
  345. // but don't pass it back to our parent directory
  346. if (hr == S_FALSE)
  347. hr = S_OK;
  348. }
  349. else
  350. TraceMsg(TF_TREEWALKER, "***WARNING***: PathCombine failed!!!!");
  351. }
  352. return hr;
  353. }
  354. // we need our own version of PathAppend that doesn't whack pszPath to '\0' in failure cases
  355. BOOL CShellTreeWalker::_PathAppend(LPTSTR pszPath, LPCTSTR pszMore)
  356. {
  357. BOOL bRet;
  358. TCHAR szTemp[MAX_PATH];
  359. // save off pszPath in case of failure so that we can restore it
  360. lstrcpyn(szTemp, pszPath, ARRAYSIZE(szTemp));
  361. bRet = PathAppend(pszPath, pszMore);
  362. if (!bRet)
  363. {
  364. // we failed, so restore pszPath
  365. lstrcpy(pszPath, szTemp);
  366. }
  367. return bRet;
  368. }
  369. // IShellTreeWalker::WalkTree is the main function for the IShellTreeWalker interface
  370. HRESULT CShellTreeWalker::WalkTree(DWORD dwFlags, LPCWSTR pwszWalkRoot, LPCWSTR pwszWalkSpec, int iMaxDepth, IShellTreeWalkerCallBack * pstwcb)
  371. {
  372. HRESULT hr = E_FAIL;
  373. TCHAR szWalkSpec[64];
  374. // must have a call back object to talk to
  375. ASSERT(IS_VALID_CODE_PTR(pstwcb, IShellTreeWalkerCackBack));
  376. if (pstwcb == NULL)
  377. return E_INVALIDARG;
  378. // make sure we have a valid directory to start with
  379. ASSERT(IS_VALID_STRING_PTRW(pwszWalkRoot, -1));
  380. if ((pwszWalkRoot != NULL) && (pwszWalkRoot[0] != L'\0'))
  381. {
  382. SHUnicodeToTChar(pwszWalkRoot, _szWalkBuf, ARRAYSIZE(_szWalkBuf));
  383. // call back
  384. _pstwcb = pstwcb;
  385. // copy the search flags and fix it up
  386. _dwFlags = dwFlags & WT_ALL;
  387. // this will save us from using the hdsa array to hold the directories
  388. if (_dwFlags & WT_FOLDERONLY)
  389. {
  390. _dwFlags |= WT_FOLDERFIRST;
  391. // It will be pretty meanless if the below flags are not set, because
  392. // we don't call FoundFile in the FolderOnly case.
  393. ASSERT(_dwFlags & (WT_NOTIFYFOLDERENTER | WT_NOTIFYFOLDERLEAVE));
  394. }
  395. if (_dwFlags & WT_FOLDERFIRST)
  396. _bFolderFirst = TRUE;
  397. if ((pwszWalkSpec != NULL) && (pwszWalkSpec[0] != L'\0'))
  398. {
  399. SHUnicodeToTChar(pwszWalkSpec, szWalkSpec, ARRAYSIZE(szWalkSpec));
  400. _pszWalkSpec = szWalkSpec;
  401. }
  402. _nMaxDepth = (dwFlags & WT_MAXDEPTH) ? iMaxDepth : 256;
  403. _dwClusterSize = PathGetClusterSize(_szWalkBuf);
  404. hr = _TreeWalkerHelper();
  405. }
  406. else
  407. TraceMsg(TF_WARNING, "CShellTreeWalker::WalkTree Failed! due to bad _szWalkBuf");
  408. return hr;
  409. }
  410. // IShellTreeWalker::QueryInterface
  411. HRESULT CShellTreeWalker::QueryInterface(REFIID riid, void ** ppv)
  412. {
  413. static const QITAB qit[] = {
  414. QITABENT(CShellTreeWalker, IShellTreeWalker),
  415. { 0 },
  416. };
  417. return QISearch(this, qit, riid, ppv);
  418. }
  419. // IShellTreeWalker::AddRef
  420. ULONG CShellTreeWalker::AddRef()
  421. {
  422. return InterlockedIncrement(&_cRef);
  423. }
  424. // IShellTreeWalker::Release
  425. ULONG CShellTreeWalker::Release()
  426. {
  427. ASSERT( 0 != _cRef );
  428. ULONG cRef = InterlockedDecrement(&_cRef);
  429. if ( 0 == cRef )
  430. {
  431. delete this;
  432. }
  433. return cRef;
  434. }
  435. STDAPI CShellTreeWalker_CreateInstance(IUnknown* pUnkOuter, REFIID riid, OUT void **ppvOut)
  436. {
  437. HRESULT hr;
  438. *ppvOut = NULL;
  439. CShellTreeWalker *pstw = new CShellTreeWalker;
  440. if (!pstw)
  441. return E_OUTOFMEMORY;
  442. hr = pstw->QueryInterface(riid, ppvOut);
  443. pstw->Release();
  444. return hr;
  445. }