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.

416 lines
12 KiB

  1. #include "shellprv.h"
  2. #include "util.h"
  3. #include "ids.h"
  4. #include "bitbuck.h"
  5. #include "mtpt.h"
  6. // states for state machine, values are relavant as we compare them
  7. // durring transitions to see if UI should be triggered or not
  8. typedef enum
  9. {
  10. STATE_1MB = 0, // the "disk is completely filled" case
  11. STATE_50MB = 1, // < 50MB case
  12. STATE_80MB = 2, // < 80MB case
  13. STATE_200MB = 3, // < 200MB case, only do this one on > 2.25GB drives
  14. STATE_ALLGOOD = 4, // > 200MB, everything is fine
  15. STATE_UNKNOWN = 5,
  16. } LOWDISK_STATE;
  17. #define BYTES_PER_MB ((ULONGLONG)0x100000)
  18. typedef struct
  19. {
  20. LOWDISK_STATE lds;
  21. ULONG dwMinMB; // range (min) that defines this state
  22. ULONG dwMaxMB; // range (max) that defines this state
  23. DWORD dwCleanupFlags; // DISKCLEANUP_
  24. DWORD dwShowTime; // in sec
  25. DWORD dwIntervalTime; // in sec
  26. UINT cRetry;
  27. DWORD niif;
  28. } STATE_DATA;
  29. #define HOURS (60 * 60)
  30. #define MINS (60)
  31. const STATE_DATA c_state_data[] =
  32. {
  33. {STATE_1MB, 0, 1, DISKCLEANUP_VERYLOWDISK, 30, 5 * MINS, -1, NIIF_ERROR},
  34. {STATE_50MB, 1, 50, DISKCLEANUP_VERYLOWDISK, 30, 5 * MINS, -1, NIIF_WARNING},
  35. {STATE_80MB, 50, 80, DISKCLEANUP_LOWDISK, 30, 4 * HOURS, 1, NIIF_WARNING},
  36. {STATE_200MB, 80, 200, DISKCLEANUP_LOWDISK, 30, 0 * HOURS, 0, NIIF_INFO},
  37. };
  38. void SRNotify(LPCWSTR pszDrive, DWORD dwFreeSpaceInMB, BOOL bImproving)
  39. {
  40. typedef void (* PFNSRNOTIFYFREESPACE)(LPCWSTR, DWORD, BOOL);
  41. static HMODULE s_hmodSR = NULL;
  42. if (NULL == s_hmodSR)
  43. s_hmodSR = LoadLibrary(TEXT("srclient.dll"));
  44. if (s_hmodSR)
  45. {
  46. PFNSRNOTIFYFREESPACE pfnNotifyFreeSpace = (PFNSRNOTIFYFREESPACE)GetProcAddress(s_hmodSR, "SRNotify");
  47. if (pfnNotifyFreeSpace)
  48. pfnNotifyFreeSpace(pszDrive, dwFreeSpaceInMB, bImproving);
  49. }
  50. }
  51. class CLowDiskSpaceUI : IQueryContinue
  52. {
  53. public:
  54. CLowDiskSpaceUI(int iDrive);
  55. void CheckDiskSpace();
  56. // IUnknown
  57. STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
  58. STDMETHODIMP_(ULONG) AddRef();
  59. STDMETHODIMP_(ULONG) Release();
  60. // IQueryContinue
  61. STDMETHODIMP QueryContinue(); // S_OK -> Continue, other
  62. private:
  63. ~CLowDiskSpaceUI();
  64. BOOL _EnterExclusive();
  65. void _LeaveExclusive();
  66. void _DoNotificationUI();
  67. void _DoStateMachine();
  68. const STATE_DATA *_StateData(LOWDISK_STATE lds);
  69. LOWDISK_STATE _StateFromFreeSpace(ULARGE_INTEGER ulTotal, ULARGE_INTEGER ulFree);
  70. LOWDISK_STATE _GetCurrentState(BOOL bInStateMachine);
  71. static DWORD CALLBACK s_ThreadProc(void *pv);
  72. LONG _cRef;
  73. TCHAR _szRoot[5];
  74. HANDLE _hMutex;
  75. LOWDISK_STATE _ldsCurrent;
  76. BOOL _bShowUI;
  77. BOOL _bSysVolume;
  78. DWORD _dwLastFreeMB;
  79. };
  80. CLowDiskSpaceUI::CLowDiskSpaceUI(int iDrive) : _cRef(1), _ldsCurrent(STATE_UNKNOWN)
  81. {
  82. ASSERT(_bShowUI == FALSE);
  83. ASSERT(_bSysVolume == FALSE);
  84. PathBuildRoot(_szRoot, iDrive);
  85. TCHAR szWinDir[MAX_PATH];
  86. if (GetWindowsDirectory(szWinDir, ARRAYSIZE(szWinDir)))
  87. {
  88. _bSysVolume = szWinDir[0] == _szRoot[0];
  89. }
  90. }
  91. CLowDiskSpaceUI::~CLowDiskSpaceUI()
  92. {
  93. if (_hMutex)
  94. CloseHandle(_hMutex);
  95. }
  96. HRESULT CLowDiskSpaceUI::QueryInterface(REFIID riid, void **ppv)
  97. {
  98. static const QITAB qit[] =
  99. {
  100. QITABENT(CLowDiskSpaceUI, IQueryContinue),
  101. { 0 },
  102. };
  103. return QISearch(this, qit, riid, ppv);
  104. }
  105. STDMETHODIMP_(ULONG) CLowDiskSpaceUI::AddRef()
  106. {
  107. return InterlockedIncrement(&_cRef);
  108. }
  109. STDMETHODIMP_(ULONG) CLowDiskSpaceUI::Release()
  110. {
  111. ASSERT(0 != _cRef);
  112. ULONG cRef = InterlockedDecrement(&_cRef);
  113. if (0 == cRef)
  114. {
  115. delete this;
  116. }
  117. return cRef;
  118. }
  119. HRESULT CLowDiskSpaceUI::QueryContinue()
  120. {
  121. LOWDISK_STATE ldsOld = _ldsCurrent;
  122. return ldsOld == _GetCurrentState(TRUE) ? S_OK : S_FALSE;
  123. }
  124. const STATE_DATA *CLowDiskSpaceUI::_StateData(LOWDISK_STATE lds)
  125. {
  126. for (int i = 0; i < ARRAYSIZE(c_state_data); i++)
  127. {
  128. if (c_state_data[i].lds == lds)
  129. return &c_state_data[i];
  130. }
  131. return NULL;
  132. }
  133. LOWDISK_STATE CLowDiskSpaceUI::_StateFromFreeSpace(ULARGE_INTEGER ulTotal, ULARGE_INTEGER ulFree)
  134. {
  135. ULONGLONG ulTotalMB = (ulTotal.QuadPart / BYTES_PER_MB);
  136. ULONGLONG ulFreeMB = (ulFree.QuadPart / BYTES_PER_MB);
  137. _dwLastFreeMB = (DWORD)ulFreeMB;
  138. for (int i = 0; i < ARRAYSIZE(c_state_data); i++)
  139. {
  140. // total needs to be 8 times the max of this range for us to consider it
  141. if ((ulTotalMB / 8) > c_state_data[i].dwMaxMB)
  142. {
  143. if ((c_state_data[i].lds == _ldsCurrent) ?
  144. ((ulFreeMB >= c_state_data[i].dwMinMB) && (ulFreeMB <= (c_state_data[i].dwMaxMB + 3))) :
  145. ((ulFreeMB >= c_state_data[i].dwMinMB) && (ulFreeMB <= c_state_data[i].dwMaxMB)))
  146. {
  147. // only report 200MB state on drives >= 2.25GB
  148. if ((c_state_data[i].lds != STATE_200MB) || (ulTotal.QuadPart >= (2250 * BYTES_PER_MB)))
  149. return c_state_data[i].lds;
  150. }
  151. }
  152. }
  153. return STATE_ALLGOOD;
  154. }
  155. LOWDISK_STATE CLowDiskSpaceUI::_GetCurrentState(BOOL bInStateMachine)
  156. {
  157. LOWDISK_STATE ldsNew = STATE_ALLGOOD; // assume this in case of failure
  158. ULARGE_INTEGER ulTotal, ulFree;
  159. if (SHGetDiskFreeSpaceEx(_szRoot, NULL, &ulTotal, &ulFree))
  160. {
  161. ldsNew = _StateFromFreeSpace(ulTotal, ulFree);
  162. }
  163. if (bInStateMachine)
  164. {
  165. if (_ldsCurrent != ldsNew)
  166. {
  167. // state change
  168. // if things are getting worse need to show UI (if we are in the state machine)
  169. _bShowUI = (ldsNew < _ldsCurrent);
  170. SRNotify(_szRoot, _dwLastFreeMB, ldsNew > _ldsCurrent); // call system restore
  171. }
  172. _ldsCurrent = ldsNew;
  173. }
  174. return ldsNew;
  175. }
  176. // creates the notification icon in the tray and shows a balloon with it
  177. // this is a modal call, when it returns either the UI has timed out or the
  178. // user has clicked on the notification UI.
  179. void CLowDiskSpaceUI::_DoNotificationUI()
  180. {
  181. // assume this will be a one shot UI, but this can get reset via our callback
  182. _bShowUI = FALSE;
  183. const STATE_DATA *psd = _StateData(_ldsCurrent);
  184. if (psd && (_bSysVolume || (psd->lds <= STATE_80MB)))
  185. {
  186. IUserNotification *pun;
  187. HRESULT hr = CoCreateInstance(CLSID_UserNotification, NULL, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER, IID_PPV_ARG(IUserNotification, &pun));
  188. if (SUCCEEDED(hr))
  189. {
  190. SHFILEINFO sfi = {0};
  191. SHGetFileInfo(_szRoot, FILE_ATTRIBUTE_DIRECTORY, &sfi, sizeof(sfi),
  192. SHGFI_USEFILEATTRIBUTES | SHGFI_DISPLAYNAME |
  193. SHGFI_ICON | SHGFI_SMALLICON | SHGFI_ADDOVERLAYS);
  194. TCHAR szTitle[64], szMsg[256], szTemplate[256];
  195. UINT niif = _bSysVolume ? psd->niif : NIIF_INFO;
  196. LoadString(HINST_THISDLL, IDS_DISK_FULL_TITLE, szTitle, ARRAYSIZE(szTitle));
  197. LoadString(HINST_THISDLL, NIIF_INFO == niif ? IDS_DISK_FULL_TEXT : IDS_DISK_FULL_TEXT_SERIOUS,
  198. szTemplate, ARRAYSIZE(szTemplate));
  199. StringCchPrintf(szMsg, ARRAYSIZE(szMsg), szTemplate, sfi.szDisplayName);
  200. pun->SetIconInfo(sfi.hIcon, szTitle);
  201. pun->SetBalloonRetry(psd->dwShowTime * 1000, psd->dwIntervalTime * 1000, psd->cRetry);
  202. // pun->SetBalloonInfo(szTitle, L"<a href=\"notepad.exe\"> Click here for notepad</a>", niif);
  203. pun->SetBalloonInfo(szTitle, szMsg, niif);
  204. hr = pun->Show(SAFECAST(this, IQueryContinue *), 1 * 1000); // 1 sec poll for callback
  205. if (sfi.hIcon)
  206. DestroyIcon(sfi.hIcon);
  207. if (S_OK == hr)
  208. {
  209. // S_OK -> user click on icon or balloon
  210. LaunchDiskCleanup(NULL, DRIVEID(_szRoot), (_bSysVolume ? psd->dwCleanupFlags : 0) | DISKCLEANUP_MODAL);
  211. }
  212. pun->Release();
  213. }
  214. }
  215. }
  216. void CLowDiskSpaceUI::_DoStateMachine()
  217. {
  218. do
  219. {
  220. if (_bShowUI)
  221. {
  222. _DoNotificationUI();
  223. }
  224. else
  225. {
  226. SHProcessMessagesUntilEvent(NULL, NULL, 5 * 1000); // 5 seconds
  227. }
  228. }
  229. while (STATE_ALLGOOD != _GetCurrentState(TRUE));
  230. }
  231. BOOL CLowDiskSpaceUI::_EnterExclusive()
  232. {
  233. if (NULL == _hMutex)
  234. {
  235. TCHAR szEvent[32];
  236. StringCchPrintf(szEvent, ARRAYSIZE(szEvent), TEXT("LowDiskOn%C"), _szRoot[0]); // ok to truncate
  237. _hMutex = CreateMutex(SHGetAllAccessSA(), FALSE, szEvent);
  238. }
  239. return _hMutex && WAIT_OBJECT_0 == WaitForSingleObject(_hMutex, 0); // zero timeout
  240. }
  241. void CLowDiskSpaceUI::_LeaveExclusive()
  242. {
  243. ASSERT(_hMutex);
  244. ReleaseMutex(_hMutex);
  245. }
  246. DWORD CALLBACK CLowDiskSpaceUI::s_ThreadProc(void *pv)
  247. {
  248. CLowDiskSpaceUI *pldsui = (CLowDiskSpaceUI *)pv;
  249. if (pldsui->_EnterExclusive())
  250. {
  251. pldsui->_DoStateMachine();
  252. pldsui->_LeaveExclusive();
  253. }
  254. pldsui->Release();
  255. return 0;
  256. }
  257. void CLowDiskSpaceUI::CheckDiskSpace()
  258. {
  259. if (STATE_ALLGOOD != _GetCurrentState(FALSE))
  260. {
  261. AddRef();
  262. if (!SHCreateThread(s_ThreadProc, this, CTF_COINIT, NULL))
  263. {
  264. Release();
  265. }
  266. }
  267. }
  268. STDAPI CheckDiskSpace()
  269. {
  270. // the only caller of this is in explorer\tray.cpp
  271. // it checks against SHRestricted(REST_NOLOWDISKSPACECHECKS) on that side.
  272. for (int i = 0; i < 26; i++)
  273. {
  274. CMountPoint* pmp = CMountPoint::GetMountPoint(i);
  275. if (pmp)
  276. {
  277. if (pmp->IsFixedDisk() && !pmp->IsRemovableDevice())
  278. {
  279. CLowDiskSpaceUI *pldsui = new CLowDiskSpaceUI(i);
  280. if (pldsui)
  281. {
  282. pldsui->CheckDiskSpace();
  283. pldsui->Release();
  284. }
  285. }
  286. pmp->Release();
  287. }
  288. }
  289. return S_OK;
  290. }
  291. STDAPI_(BOOL) GetDiskCleanupPath(LPTSTR pszBuf, UINT cchSize)
  292. {
  293. if (pszBuf)
  294. *pszBuf = 0;
  295. DWORD cbLen = CbFromCch(cchSize);
  296. return SUCCEEDED(SKGetValue(SHELLKEY_HKLM_EXPLORER, TEXT("MyComputer\\cleanuppath"), NULL, NULL, pszBuf, &cbLen));
  297. }
  298. STDAPI_(void) LaunchDiskCleanup(HWND hwnd, int iDrive, UINT uFlags)
  299. {
  300. TCHAR szPathTemplate[MAX_PATH];
  301. if (GetDiskCleanupPath(szPathTemplate, ARRAYSIZE(szPathTemplate)))
  302. {
  303. TCHAR szPath[MAX_PATH], szArgs[MAX_PATH];
  304. HRESULT hr;
  305. BOOL fExec = FALSE;
  306. hr = StringCchPrintf(szPath, ARRAYSIZE(szPath), szPathTemplate, TEXT('A') + iDrive);
  307. if (SUCCEEDED(hr))
  308. {
  309. if (uFlags & DISKCLEANUP_LOWDISK)
  310. {
  311. hr = StringCchCat(szPath, ARRAYSIZE(szPath), TEXT(" /LOWDISK"));
  312. }
  313. else if (uFlags & DISKCLEANUP_VERYLOWDISK)
  314. {
  315. hr = StringCchCat(szPath, ARRAYSIZE(szPath), TEXT(" /VERYLOWDISK"));
  316. }
  317. if (SUCCEEDED(hr))
  318. {
  319. hr = PathSeperateArgs(szPath, szArgs, ARRAYSIZE(szArgs), NULL);
  320. if (SUCCEEDED(hr))
  321. {
  322. SHELLEXECUTEINFO ei =
  323. {
  324. sizeof(ei),
  325. SEE_MASK_FLAG_NO_UI | SEE_MASK_NOCLOSEPROCESS,
  326. NULL, NULL, szPath, szArgs, NULL, SW_SHOWNORMAL, NULL
  327. };
  328. if (ShellExecuteEx(&ei))
  329. {
  330. fExec = TRUE;
  331. if (ei.hProcess)
  332. {
  333. if (DISKCLEANUP_MODAL & uFlags)
  334. SHProcessMessagesUntilEvent(NULL, ei.hProcess, INFINITE);
  335. CloseHandle(ei.hProcess);
  336. }
  337. }
  338. }
  339. }
  340. }
  341. if (!fExec)
  342. {
  343. ShellMessageBox(HINST_THISDLL, NULL,
  344. MAKEINTRESOURCE(IDS_NO_CLEANMGR_APP),
  345. NULL, MB_OK | MB_ICONEXCLAMATION | MB_SETFOREGROUND);
  346. }
  347. }
  348. }
  349. // public export
  350. STDAPI_(void) SHHandleDiskFull(HWND hwnd, int idDrive)
  351. {
  352. // legacy, no one calls this
  353. }