Source code of Windows XP (NT5)
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.

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