/////////////////////////////////////////////////////////////////////////////// /* File: policy.cpp Description: Handles disk quota policy issues for both a GPE client extension and a server MMC policy snapin (see snapin.cpp). ProgressGroupPolicy is called by winlogon to process disk quota policy on the client machine. ProcessGroupPolicy instantiates a CDiskQuotaPolicy object to handle the loading and application of disk quota policy. The CDiskQuotaPolicy object is also instantiated by the MMC disk quota policy snapin to save quota policy information to the registry. A good deal of this module, especially in CDiskQuotaPolicy::Apply( ), is devoted to reporting errors to the NT event log. This is necessary because much of this code runs without UI from within winlogon. That's also why there's a lot of debugger spew. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 02/14/98 Initial creation. BrianAu 11/24/98 Added event logging settings to policy. BrianAu 11/30/98 Replaced ProcessGPO function with BrianAu ProcessGroupPolicy to support GPO interface changes. */ /////////////////////////////////////////////////////////////////////////////// #include "pch.h" #pragma hdrstop #include #include #include #include "policy.h" #include "registry.h" #include "guidsp.h" #include "msg.h" #include "resource.h" // // Global NT event log object. // CEventLog g_Log; // // Name of the disk quota dll. // const TCHAR g_szDskquotaDll[] = TEXT("dskquota.dll"); // // Exported function called by winlogon to update policy on the client machine. // // This function is registered as a GPO extension (see selfreg.inf). // DWORD ProcessGroupPolicy( DWORD dwFlags, HANDLE hUserToken, HKEY hkeyRoot, PGROUP_POLICY_OBJECT pDeletedGPOList, PGROUP_POLICY_OBJECT pChangedGPOList, ASYNCCOMPLETIONHANDLE pHandle, BOOL *pbAbort, PFNSTATUSMESSAGECALLBACK pStatusCallback ) { HRESULT hr = ERROR_SUCCESS; DBGTRACE((DM_POLICY, DL_HIGH, TEXT("ProcessGroupPolicy"))); DBGPRINT((DM_POLICY, DL_LOW, TEXT("\tdwFlags......: 0x%08X"), dwFlags)); DBGPRINT((DM_POLICY, DL_LOW, TEXT("\thUserToken...: 0x%08X"), hUserToken)); DBGPRINT((DM_POLICY, DL_LOW, TEXT("\thKeyRoot.....: 0x%08X"), hkeyRoot)); // // FEATURE: Need to add support for pDeletedGPOList // If pDeletedGPOList is non-null, you should // reset the disk quotas back to their defaults first // and then apply the new settings below if appropriate // if (pChangedGPOList) { hr = g_Log.Initialize(TEXT("DiskQuota")); if (FAILED(hr)) { DBGERROR((TEXT("Error 0x%08X initializing NT event log."), hr)); // // Continue without event log. // } // // Only process policy info when... // // 1. Not deleting policy. // try { DBGPRINT((DM_POLICY, DL_HIGH, TEXT("Set quota policy - START."))); autoptr ptrPolicy(new CDiskQuotaPolicy(NULL, hkeyRoot, 0 != (GPO_INFO_FLAG_VERBOSE & dwFlags), pbAbort)); DISKQUOTAPOLICYINFO dqpi; ZeroMemory(&dqpi, sizeof(dqpi)); // // Load policy info from the registry and apply to local volumes. // hr = ptrPolicy->Load(&dqpi); if (SUCCEEDED(hr)) { hr = ptrPolicy->Apply(&dqpi); } DBGPRINT((DM_POLICY, DL_HIGH, TEXT("Set quota policy - FINISHED."))); } catch(CAllocException& e) { DBGERROR((TEXT("Insufficient memory in ProcessGroupPolicy"))); g_Log.ReportEvent(EVENTLOG_ERROR_TYPE, 0, MSG_E_POLICY_OUTOFMEMORY); hr = E_OUTOFMEMORY; } } return hr; } //----------------------------------------------------------------------------- // CDiskQuotaPolicy //----------------------------------------------------------------------------- // // Location of disk quota policy information in the registry. The "PolicyData" // value name is somewhat arbitrary. The policy key name string however must // coordinate with other system policy locations in the registry. It should // not change unless you have a good reason to do so and you understand the // consequences. // const TCHAR CDiskQuotaPolicy::m_szRegKeyPolicy[] = REGSTR_KEY_POLICYDATA; #ifdef POLICY_MMC_SNAPIN const TCHAR CDiskQuotaPolicy::m_szRegValPolicy[] = REGSTR_VAL_POLICYDATA; #endif CDiskQuotaPolicy::CDiskQuotaPolicy( LPGPEINFORMATION pGPEInfo, HKEY hkeyRoot, bool bVerboseEventLog, BOOL *pbAbort ) : m_cRef(0), m_pGPEInfo(pGPEInfo), m_hkeyRoot(hkeyRoot), m_pbAbort(pbAbort), m_bRootKeyOpened(false), m_bVerboseEventLog(bVerboseEventLog) { DBGTRACE((DM_POLICY, DL_MID, TEXT("CDiskQuotaPolicy::CDiskQuotaPolicy"))); } CDiskQuotaPolicy::~CDiskQuotaPolicy( void ) { DBGTRACE((DM_POLICY, DL_MID, TEXT("CDiskQuotaPolicy::~CDiskQuotaPolicy"))); if (NULL != m_hkeyRoot && m_bRootKeyOpened) RegCloseKey(m_hkeyRoot); if (NULL != m_pGPEInfo) m_pGPEInfo->Release(); } HRESULT CDiskQuotaPolicy::QueryInterface( REFIID riid, LPVOID *ppvOut ) { DBGTRACE((DM_POLICY, DL_MID, TEXT("CDiskQuotaPolicy::~QueryInterface"))); HRESULT hr = E_NOINTERFACE; *ppvOut = NULL; if (IID_IUnknown == riid || IID_IDiskQuotaPolicy == riid) { *ppvOut = this; ((LPUNKNOWN)*ppvOut)->AddRef(); hr = NOERROR; } return hr; } ULONG CDiskQuotaPolicy::AddRef( void ) { DBGTRACE((DM_POLICY, DL_LOW, TEXT("CDiskQuotaPolicy::AddRef"))); ULONG ulReturn = m_cRef + 1; InterlockedIncrement(&m_cRef); return ulReturn; } ULONG CDiskQuotaPolicy::Release( void ) { DBGTRACE((DM_POLICY, DL_LOW, TEXT("CDiskQuotaPolicy::Release"))); ULONG ulReturn = m_cRef - 1; if (InterlockedDecrement(&m_cRef) == 0) { delete this; ulReturn = 0; } return ulReturn; } // // Caller can init with either: // // 1. Ptr to IGPEInformation interface. The snap in should initialize this // way since it has a pointer to an IGPEInformation // interface (LPGPEINFORMATION). // // 2. HKEY retrieved from IGPEInformation interface or from Group Policy // notification. ProcessGroupPolicy should initialize this way since it is given // the root key from winlogon. // // Can also init with both but pGPEInfo will be ignored if hkeyRoot is provided. // HRESULT CDiskQuotaPolicy::Initialize( LPGPEINFORMATION pGPEInfo, HKEY hkeyRoot ) { DBGTRACE((DM_POLICY, DL_MID, TEXT("CDiskQuotaPolicy::Initialize"))); if (NULL != m_pGPEInfo || NULL != m_hkeyRoot) return S_FALSE; // Already intialized m_hkeyRoot = hkeyRoot; m_pGPEInfo = pGPEInfo; if (NULL != m_pGPEInfo) m_pGPEInfo->AddRef(); return S_OK; } // // Fill in a DISKQUOTAPOLICYINFO structure with default data. // void CDiskQuotaPolicy::InitPolicyInfo( LPDISKQUOTAPOLICYINFO pInfo ) { pInfo->llDefaultQuotaThreshold = (LONGLONG)-1; // No limit. pInfo->llDefaultQuotaLimit = (LONGLONG)-1; // No limit. pInfo->dwQuotaState = 0; pInfo->dwQuotaLogFlags = 0; pInfo->bRemovableMedia = 0; } // // Initialize and load policy information into a DISKQUOTAPOLICYINFO structure. // If reg values don't exist, default values are used. // void CDiskQuotaPolicy::LoadPolicyInfo( const RegKey& key, LPDISKQUOTAPOLICYINFO pInfo ) { DWORD dwValue = DWORD(-1); const struct { LPCTSTR pszValue; // Name of the "value" reg value. LPCTSTR pszUnits; // Name of the "units" reg value. LONGLONG *pValue; // Address of destination for computed limit or threshold. } rgValUnits[] = { { REGSTR_VAL_POLICY_LIMIT, REGSTR_VAL_POLICY_LIMITUNITS, &(pInfo->llDefaultQuotaLimit) }, { REGSTR_VAL_POLICY_THRESHOLD, REGSTR_VAL_POLICY_THRESHOLDUNITS, &(pInfo->llDefaultQuotaThreshold) } }; // // Initialize with defaults. // InitPolicyInfo(pInfo); // // Load the limit and threshold values along with their respective "units" // factor. The factor is a number [1..6] that represents the required // multiplier to convert the "value" to a byte value. // // 1 = KB, 2 = MB, 3 = GB, 4 = TB, 5 = PB, 6 = EB // // Bytes = value << (factor * 10). // // Given: value = 250 // factor = 2 (MB) // // Bytes = 250 << 20 // = 262,144,000 // = 250 MB // for (int i = 0; i < ARRAYSIZE(rgValUnits); i++) { dwValue = DWORD(-1); DWORD dwUnits = DWORD(-1); key.GetValue(rgValUnits[i].pszValue, &dwValue); key.GetValue(rgValUnits[i].pszUnits, &dwUnits); *(rgValUnits[i].pValue) = LONGLONG(-1); // must cast dwValue to "int" since DWORDs are never negative // Don't need to test dwUnits for negative since negative DWORDs // turn into huge positive values, so it will fail the "6 >= dwUnits" // test. if (0 <= (int)dwValue && 6 >= dwUnits) { *(rgValUnits[i].pValue) = LONGLONG(dwValue) << (10 * dwUnits); } } // // This logic for setting the dwQuotaState member is the same as that // used in VolumePropPage::QuotaStateFromControls (volprop.cpp). // DWORD dwEnable = 0; DWORD dwEnforce = 0; key.GetValue(REGSTR_VAL_POLICY_ENABLE, &dwEnable); key.GetValue(REGSTR_VAL_POLICY_ENFORCE, &dwEnforce); if (dwEnable) { if (dwEnforce) { pInfo->dwQuotaState = DISKQUOTA_STATE_ENFORCE; } else { pInfo->dwQuotaState = DISKQUOTA_STATE_TRACK; } } else { pInfo->dwQuotaState = DISKQUOTA_STATE_DISABLED; } // // Get event logging settings. // DWORD dwLog = 0; key.GetValue(REGSTR_VAL_POLICY_LOGLIMIT, &dwLog); DISKQUOTA_SET_LOG_USER_LIMIT(pInfo->dwQuotaLogFlags, dwLog); dwLog = 0; key.GetValue(REGSTR_VAL_POLICY_LOGTHRESHOLD, &dwLog); DISKQUOTA_SET_LOG_USER_THRESHOLD(pInfo->dwQuotaLogFlags, dwLog); // // Determine if policy is to be applied to removable as well as fixed // media. // if (SUCCEEDED(key.GetValue(REGSTR_VAL_POLICY_REMOVABLEMEDIA, &dwValue))) { pInfo->bRemovableMedia = boolify(dwValue); } } // // Load machine policy information from the registry. See comment // in CDiskQuotaPolicy::Save( ) for registry location information. // HRESULT CDiskQuotaPolicy::Load( LPDISKQUOTAPOLICYINFO pInfo ) { DBGTRACE((DM_POLICY, DL_MID, TEXT("CDiskQuotaPolicy::Load"))); DBGASSERT((NULL != pInfo)); if (NULL == m_pGPEInfo && NULL == m_hkeyRoot) { DBGERROR((TEXT("Policy object not initialized"))); return E_FAIL; // Not initialized. } HRESULT hr = E_FAIL; if (NULL == m_hkeyRoot && SUCCEEDED(hr = m_pGPEInfo->GetRegistryKey(GPO_SECTION_MACHINE, &m_hkeyRoot))) { m_bRootKeyOpened = true; } if (NULL != m_hkeyRoot) { DBGPRINT((DM_POLICY, DL_LOW, TEXT("Opening reg key 0x%08X \"%s\""), m_hkeyRoot, m_szRegKeyPolicy)); RegKey key(m_hkeyRoot, m_szRegKeyPolicy); hr = key.Open(KEY_READ); if (SUCCEEDED(hr)) { DBGPRINT((DM_POLICY, DL_LOW, TEXT("Reading disk quota policy information."))); LoadPolicyInfo(key, pInfo); if (m_bVerboseEventLog) { // // Report successful information retrieval. // g_Log.ReportEvent(EVENTLOG_INFORMATION_TYPE, 0, MSG_I_POLICY_INFOLOADED); } } else if (ERROR_FILE_NOT_FOUND != HRESULT_CODE(hr)) // Key doesn't always exist. { DBGERROR((TEXT("Error 0x%08X opening policy reg key"), hr)); g_Log.Push(hr, CEventLog::eFmtHex); g_Log.Push(m_szRegKeyPolicy), g_Log.Push(hr, CEventLog::eFmtSysErr); g_Log.ReportEvent(EVENTLOG_ERROR_TYPE, 0, MSG_E_POLICY_GPEREGKEYOPEN); } } else { DBGERROR((TEXT("m_hkeyRoot is NULL"))); g_Log.Push(hr, CEventLog::eFmtHex); g_Log.Push(hr, CEventLog::eFmtSysErr); g_Log.ReportEvent(EVENTLOG_ERROR_TYPE, 0, MSG_E_POLICY_GPEREGKEYROOT); } return hr; } #ifdef POLICY_MMC_SNAPIN // // NOTE: This code has been disabled because we're moving from using an MMC // snapin to an ADM-file format that fits better into the current // MMC software policy scheme. This Save() function was required for // the snapin implementation but is not required when we use an ADM file. // The function was originally written to store a whole DISKQUOTAPOLICYINFO // structure in the registry and the original version of Load() was // written to read a whole DISKQUOTAPOLICYINFO structure as REG_BINARY. // Since the ADM file works with specific reg values rather than a single // REG_BINARY, Load() was modified to work with values stored using // the ADM format. Save() has not been modified. // // If we reactivate this function to use in a snapin, we need to // update it to write out data in a format acceptable to Load(). // [brianau - 6/25/98] // // This function saves the policy info to the following registry value on the // local machine. // // HKCU\Software\Microsoft\GPE\{98E1D3C1-9DC1-11D1-8544-0000F8046117}Machine\Software\Policies\Microsoft\Windows NT\DiskQuota\PolicyData" // // The call to PolicyChanged( ) flushes the data to the server file: // // \\\SysVol\Policies\{98E1D3C1-9DC1-11D1-8544-0000F8046117}\machine\registry.pol // // where is the name of the server. // // HRESULT CDiskQuotaPolicy::Save( LPCDISKQUOTAPOLICYINFO pInfo ) { DBGTRACE((DM_POLICY, DL_MID, TEXT("CDiskQuotaPolicy::Save"))); DBGASSERT((NULL != pInfo)); if (NULL == m_pGPEInfo && NULL == m_hkeyRoot) { DBGERROR((TEXT("Policy object not initialized"))); return E_FAIL; // Not initialized. } HRESULT hr = E_FAIL; if (NULL == m_hkeyRoot && SUCCEEDED(hr = m_pGPEInfo->GetRegistryKey(GPO_SECTION_MACHINE, &m_hkeyRoot))) { m_bRootKeyOpened = true; } if (NULL != m_hkeyRoot) { DBGPRINT((DM_POLICY, DL_LOW, TEXT("Creating reg key 0x%08X \"%s\""), m_hkeyRoot, m_szRegKeyPolicy)); RegKey key(m_hkeyRoot, m_szRegKeyPolicy); hr = key.Open(KEY_WRITE, true); if (SUCCEEDED(hr)) { DBGPRINT((DM_POLICY, DL_LOW, TEXT("Setting reg value \"%s\""), m_szRegValPolicy)); hr = key.SetValue(m_szRegValPolicy, (LPBYTE)pInfo, sizeof(*pInfo)); if (SUCCEEDED(hr)) { DBGPRINT((DM_POLICY, DL_LOW, TEXT("Calling PolicyChanged()."))); if (FAILED(hr = m_pGPEInfo->PolicyChanged(TRUE))) DBGERROR((TEXT("Error 0x%08X returned by PolicyChanged()"), hr)); } else DBGERROR((TEXT("Error 0x%08X setting policy reg value"), hr)); } else DBGERROR((TEXT("Error 0x%08X opening policy reg key"), hr)); } else DBGERROR((TEXT("m_hkeyRoot is NULL"))); return hr; } #endif // POLICY_MMC_SNAPIN // // Apply policy information to all local NTFS volumes. Removable media // are optional per a value in the policy information structure. // HRESULT CDiskQuotaPolicy::Apply( LPCDISKQUOTAPOLICYINFO pInfo ) { DBGTRACE((DM_POLICY, DL_MID, TEXT("CDiskQuotaPolicy::Apply"))); DBGASSERT((NULL != pInfo)); HRESULT hr = NOERROR; BOOL bAborted = m_pbAbort ? *m_pbAbort : FALSE; try { if (!bAborted) { CString strVolCompleted; // // Get list of drives on which to set policy. // CArray rgstrDrives; hr = GetDriveNames(&rgstrDrives, pInfo->bRemovableMedia); int cDrives = rgstrDrives.Count(); if (SUCCEEDED(hr) && 0 < cDrives) { // // Get the disk quota class factory. This way we don't // call CoCreateInstance for each drive. Only call it once then // call the class factory's CreateInstance for each drive. // Should be more efficient. // com_autoptr pcf; hr = CoCreateInstance(CLSID_DiskQuotaControl, NULL, CLSCTX_INPROC_SERVER, IID_IClassFactory, reinterpret_cast(pcf.getaddr())); if (SUCCEEDED(hr)) { bAborted = m_pbAbort ? *m_pbAbort : FALSE; for (int i = 0; i < cDrives && !bAborted; i++) { DBGPRINT((DM_POLICY, DL_MID, TEXT("Setting policy for \"%s\""), rgstrDrives[i].Cstr())); // // Get a quota control object and initialize it for drive[i]. // Init with read/write access. // com_autoptr pdqc; hr = pcf->CreateInstance(NULL, IID_IDiskQuotaControl, reinterpret_cast(pdqc.getaddr())); if (SUCCEEDED(hr)) { hr = pdqc->Initialize(rgstrDrives[i], TRUE); if (SUCCEEDED(hr)) { // // Set the quota information on the volume. // if (FAILED(hr = pdqc->SetQuotaLogFlags(pInfo->dwQuotaLogFlags))) { DBGERROR((TEXT("Error 0x%08X setting log flags"), hr)); goto setpolerr; } if (FAILED(hr = pdqc->SetDefaultQuotaThreshold(pInfo->llDefaultQuotaThreshold))) { DBGERROR((TEXT("Error 0x%08X setting default threshold"), hr)); goto setpolerr; } if (FAILED(hr = pdqc->SetDefaultQuotaLimit(pInfo->llDefaultQuotaLimit))) { DBGERROR((TEXT("Error 0x%08X setting default limit"), hr)); goto setpolerr; } // // Set state last in case we're enabling quotas. That way // any rebuild activity will come after the other settings have // been set. // if (FAILED(hr = pdqc->SetQuotaState(DISKQUOTA_STATE_MASK & pInfo->dwQuotaState))) { DBGERROR((TEXT("Error 0x%08X setting quota state"), hr)); goto setpolerr; } goto setpolsuccess; } else DBGERROR((TEXT("Error 0x%08X initializing vol \"%s\""), hr, rgstrDrives[i].Cstr())); setpolerr: // // Record error for this particular volume. // g_Log.Push(hr, CEventLog::eFmtHex); g_Log.Push(rgstrDrives[i].Cstr()); g_Log.Push(hr, CEventLog::eFmtSysErr); g_Log.ReportEvent(EVENTLOG_ERROR_TYPE, 0, MSG_E_POLICY_SETQUOTA); setpolsuccess: if (m_bVerboseEventLog && SUCCEEDED(hr)) { // // Append name to list of drives that have been successful. // strVolCompleted += rgstrDrives[i]; strVolCompleted += CString(TEXT(" ")); } pdqc = NULL; // This releases pdqc. } else { DBGERROR((TEXT("CreateInstance failed with error 0x%08X"), hr)); g_Log.Push(hr, CEventLog::eFmtHex); g_Log.Push(hr, CEventLog::eFmtSysErr); g_Log.ReportEvent(EVENTLOG_ERROR_TYPE, 0, MSG_E_POLICY_CREATEQUOTACONTROL); } } pcf = NULL; // This releases pcf. } else { DBGERROR((TEXT("CoCreateInstance failed with error 0x%08X"), hr)); g_Log.Push(hr, CEventLog::eFmtHex); g_Log.Push(hr, CEventLog::eFmtSysErr); g_Log.ReportEvent(EVENTLOG_ERROR_TYPE, 0, MSG_E_POLICY_CREATECLASSFACTORY); } } else { DBGERROR((TEXT("Error 0x%08X getting drive name list"), hr)); g_Log.Push(hr, CEventLog::eFmtHex); g_Log.Push(hr, CEventLog::eFmtSysErr); g_Log.ReportEvent(EVENTLOG_ERROR_TYPE, 0, MSG_E_POLICY_GETDRIVELIST); } if (m_bVerboseEventLog && 0 < strVolCompleted.Length()) { // // Log successful completions by listing volumes // and applied policy values. // CString s; LONGLONG llValue; g_Log.Push(strVolCompleted); g_Log.Push(!DISKQUOTA_IS_DISABLED(pInfo->dwQuotaState)); g_Log.Push(DISKQUOTA_IS_ENFORCED(pInfo->dwQuotaState)); llValue = pInfo->llDefaultQuotaThreshold; if (LONGLONG(-1) != llValue) { XBytes::FormatByteCountForDisplay(llValue, s.GetBuffer(40), 40); s.ReleaseBuffer(); } else { s.Format(g_hInstDll, IDS_NO_LIMIT); } g_Log.Push(s); llValue = pInfo->llDefaultQuotaLimit; if (LONGLONG(-1) != llValue) { XBytes::FormatByteCountForDisplay(llValue, s.GetBuffer(40), 40); s.ReleaseBuffer(); } else { s.Format(g_hInstDll, IDS_NO_LIMIT); } g_Log.Push(s); g_Log.ReportEvent(EVENTLOG_INFORMATION_TYPE, 0, MSG_I_POLICY_FINISHED); } } } catch(CAllocException& e) { DBGERROR((TEXT("Insufficient memory."))); hr = E_OUTOFMEMORY; g_Log.ReportEvent(EVENTLOG_ERROR_TYPE, 0, MSG_E_POLICY_OUTOFMEMORY); } if (bAborted) { g_Log.ReportEvent(EVENTLOG_INFORMATION_TYPE, 0, MSG_I_POLICY_ABORTED); } return hr; } // // Build a list of drives to which policy can be applied. // HRESULT CDiskQuotaPolicy::GetDriveNames( // [ static ] CArray *prgstrDrives, // Output array of name strings. bool bRemovableMedia // Include removable media? ) { DBGTRACE((DM_POLICY, DL_MID, TEXT("CDiskQuotaPolicy::GetDriveNames"))); DBGASSERT((NULL != prgstrDrives)); HRESULT hr = NOERROR; // // Get buffer size required to hold drive name strings. // int cch = GetLogicalDriveStrings(0, NULL); // // Allocate buffer and get the strings. // array_autoptr ptrDrives(new TCHAR[cch + 1]); if (0 < GetLogicalDriveStrings(cch, ptrDrives.get())) { // // Iterate over all of the drive name strings. Append to the // string array each that can accept policy. // DblNulTermListIter iter(ptrDrives.get()); LPCTSTR pszDrive; while(iter.Next(&pszDrive)) { if (S_OK == OkToApplyPolicy(pszDrive, bRemovableMedia)) { prgstrDrives->Append(CString(pszDrive)); } } } else { DWORD dwErr = GetLastError(); DBGERROR((TEXT("GetLogicalDriveStrings failed with error %d"), dwErr)); hr = HRESULT_FROM_WIN32(dwErr); } return hr; } // // Returns: S_OK = OK to set policy on drive. // S_FALSE = Not OK to set policy // Other = Error occured. Not OK to set policy. // HRESULT CDiskQuotaPolicy::OkToApplyPolicy( // [ static ] LPCTSTR pszDrive, // Drive (volume) name string. bool bRemovableMedia // Include removable media? ) { DBGTRACE((DM_POLICY, DL_LOW, TEXT("CDiskQuotaPolicy::OkToApplyPolicy"))); HRESULT hr = S_FALSE; // // Primary filter is drive type. // UINT uDriveType = GetDriveType(pszDrive); switch(uDriveType) { case DRIVE_UNKNOWN: DBGPRINT((DM_POLICY, DL_LOW, TEXT("\"%s\" is UNKNOWN"), pszDrive)); return S_FALSE; case DRIVE_NO_ROOT_DIR: DBGPRINT((DM_POLICY, DL_LOW, TEXT("\"%s\" has no root dir"), pszDrive)); return S_FALSE; case DRIVE_REMOTE: DBGPRINT((DM_POLICY, DL_LOW, TEXT("\"%s\" is REMOTE"), pszDrive)); return S_FALSE; case DRIVE_CDROM: DBGPRINT((DM_POLICY, DL_LOW, TEXT("\"%s\" is CDROM"), pszDrive)); return S_FALSE; case DRIVE_RAMDISK: DBGPRINT((DM_POLICY, DL_LOW, TEXT("\"%s\" is RAMDISK"), pszDrive)); return S_FALSE; case DRIVE_REMOVABLE: // // Removable is allowable if policy says it is. It should be // disallowed by default since using disk quota on removable media // doesn't make a lot of sense in most situations. // if (!bRemovableMedia) { DBGPRINT((DM_POLICY, DL_LOW, TEXT("\"%s\" is REMOVABLE"), pszDrive)); return S_FALSE; } // // Fall through... // case DRIVE_FIXED: // // Fixed drives are always acceptable. // break; default: DBGERROR((TEXT("Unknown drive type %d for \"%s\""), uDriveType, pszDrive)); return S_FALSE; } // // Next filter is support for NTFS quotas. We do the drive-type check first // because it doesn't require hitting the disks. GetVolumeInformation does // hit the disk so we only want to do it if necessary. // DWORD dwFlags = 0; if (GetVolumeInformation(pszDrive, NULL, 0, NULL, NULL, &dwFlags, NULL, 0)) { if (FILE_VOLUME_QUOTAS & dwFlags) { DBGPRINT((DM_POLICY, DL_LOW, TEXT("Ok to set policy on \"%s\""), pszDrive)); hr = S_OK; } else { DBGPRINT((DM_POLICY, DL_LOW, TEXT("\"%s\" doesn't support NTFS quotas"), pszDrive)); } } else { DWORD dwErr = GetLastError(); DBGERROR((TEXT("GetVolumeInformation failed with error %d for \"%s\""), dwErr, pszDrive)); hr = HRESULT_FROM_WIN32(dwErr); } return hr; }