/////////////////////////////////////////////////////////////////////////////// /* File: fsobject.cpp Description: Contains member function definitions for class FSObject and it's derived subclasses. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/22/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// #include "pch.h" // PCH #pragma hdrstop #include "dskquota.h" #include "fsobject.h" #include "pathstr.h" // // Verify that build is UNICODE. // #if !defined(UNICODE) # error This module must be compiled UNICODE. #endif /////////////////////////////////////////////////////////////////////////////// /* Function: FSObject::~FSObject Description: Destructor. Frees object's name buffer. Arguments: None. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/22/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// FSObject::~FSObject( VOID ) { DBGTRACE((DM_CONTROL, DL_MID, TEXT("FSObject::~FSObject"))); } /////////////////////////////////////////////////////////////////////////////// /* Function: FSObject::AddRef Description: Increments object reference count. Note this is not a member of IUnknown; but it works the same. Arguments: None. Returns: New reference count value. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/22/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// ULONG FSObject::AddRef( VOID ) { DBGTRACE((DM_CONTROL, DL_LOW, TEXT("FSObject::AddRef"))); ULONG cRef = InterlockedIncrement(&m_cRef); DBGPRINT((DM_CONTROL, DL_LOW, TEXT("\t0x%08X %d -> %d"), this, cRef - 1, cRef )); return cRef; } /////////////////////////////////////////////////////////////////////////////// /* Function: FSObject::Release Description: Decrements object reference count. If count drops to 0, object is deleted. Note this is not a member of IUnknown; but it works the same. Arguments: None. Returns: New reference count value. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/22/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// ULONG FSObject::Release( VOID ) { DBGTRACE((DM_CONTROL, DL_LOW, TEXT("FSObject::Release"))); ASSERT( 0 != m_cRef ); ULONG cRef = InterlockedDecrement(&m_cRef); DBGPRINT((DM_CONTROL, DL_LOW, TEXT("\t0x%08X %d -> %d"), this, cRef + 1, cRef )); if ( 0 == cRef ) { delete this; } return cRef; } /////////////////////////////////////////////////////////////////////////////// /* Function: FSObject::ObjectSupportsQuotas Description: Determine a file system object's type (and locality) from it's name string. Arguments: pszFSObjName - Volume root name. (i.e. "C:\", "\\scratch\scratch"). Returns: S_OK - Success. Supports quotas. ERROR_NOT_SUPPORTED (hr) - File system doesn't support quotas. Other win32 error - Couldn't get volume information. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/24/96 Initial creation. BrianAu 08/16/96 Added pSupportsQuotas. BrianAu 12/05/96 Disabled check for $DeadMeat volume label. BrianAu Leave the code in place for a while. I'll remove it later when we're sure it's not needed. 07/03/97 Changed name from ObjectTypeFromName. BrianAu Changed logic to indicate reason for not supporting quotas. */ /////////////////////////////////////////////////////////////////////////////// HRESULT FSObject::ObjectSupportsQuotas( LPCTSTR pszFSObjName ) { DBGTRACE((DM_CONTROL, DL_MID, TEXT("FSObject::ObjectSupportsQuotas"))); DBGPRINT((DM_CONTROL, DL_MID, TEXT("\tobject = \"%s\""), pszFSObjName ? pszFSObjName : TEXT(""))); HRESULT hr = E_FAIL; DWORD dwFileSysFlags = 0; TCHAR szFileSysName[MAX_PATH]; DBGASSERT((NULL != pszFSObjName)); if (GetVolumeInformation( pszFSObjName, NULL, 0, NULL, 0, &dwFileSysFlags, szFileSysName, ARRAYSIZE(szFileSysName))) { // // Does the file system support quotas? // if (0 != (dwFileSysFlags & FILE_VOLUME_QUOTAS)) { // // Yes, it does. // hr = S_OK; DBGPRINT((DM_CONTROL, DL_LOW, TEXT("Vol \"%s\" supports quotas"), pszFSObjName)); } else { // // Doesn't support quotas. // hr = HRESULT_FROM_WIN32(ERROR_NOT_SUPPORTED); DBGPRINT((DM_CONTROL, DL_HIGH, TEXT("File system \"%s\" on \"%s\" doesn't support quotas."), szFileSysName, pszFSObjName)); } } else { DWORD dwErr = GetLastError(); hr = HRESULT_FROM_WIN32(dwErr); DBGERROR((TEXT("Error %d calling GetVolumeInformation for \"%s\""), dwErr, pszFSObjName)); } return hr; } /////////////////////////////////////////////////////////////////////////////// /* Function: FSObject::Create Description: 2 overloaded functions. Static functions for creating a File System object of the proper type. Clients call Create with an object name string or a reference to an existing FSObject instance. Arguments: pszFSObjName - Address of volume root string. ppNewObject - Address of FSObject pointer to accept the address of the new file system object. ObjToClone - Reference to file system object to be cloned. Returns: NOERROR - Success. E_OUTOFMEMORY - Insufficient memory. ERROR_ACCESS_DENIED (hr) - Insufficient access to open device. ERROR_FILE_NOT_FOUND (hr) - Disk device not found. ERROR_INVALID_NAME (hr) - Object name is invalid. ERROR_NOT_SUPPORTED (hr) - Volume doesn't support quotas. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/23/96 Initial creation. BrianAu 09/05/96 Added exception handling. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT FSObject::Create( LPCTSTR pszFSObjName, DWORD dwAccess, FSObject **ppNewObject ) { DBGTRACE((DM_CONTROL, DL_MID, TEXT("FSObject::Create"))); DBGPRINT((DM_CONTROL, DL_MID, TEXT("\tVol = \"%s\""), pszFSObjName ? pszFSObjName : TEXT(""))); HRESULT hr = NOERROR; DBGASSERT((NULL != pszFSObjName)); DBGASSERT((NULL != ppNewObject)); *ppNewObject = NULL; FSObject *pNewObject = NULL; try { hr = FSObject::ObjectSupportsQuotas(pszFSObjName); if (SUCCEEDED(hr)) { hr = FSObject_CreateLocalVolume(pszFSObjName, &pNewObject); if (SUCCEEDED(hr)) { // // Do any subclass-specific initialization. // i.e.: Volume opens the volume device. // hr = pNewObject->Initialize(dwAccess); if (SUCCEEDED(hr)) { // // Return ptr to caller. // DBGPRINT((DM_CONTROL, DL_MID, TEXT("FSObject created"))); pNewObject->AddRef(); *ppNewObject = pNewObject; } else { DBGPRINT((DM_CONTROL, DL_MID, TEXT("FSObject create FAILED with error 0x%08X"), hr)); delete pNewObject; pNewObject = NULL; } } } } catch(CAllocException& e) { DBGERROR((TEXT("Insufficient memory exception"))); delete pNewObject; // Will also free name if necessary. hr = E_OUTOFMEMORY; } return hr; } // // Version to clone an existing FSObject. // HRESULT FSObject::Create( const FSObject& ObjectToClone, FSObject **ppNewObject ) { return FSObject::Create(ObjectToClone.m_strFSObjName, ObjectToClone.m_dwAccessRights, ppNewObject); } /////////////////////////////////////////////////////////////////////////////// /* Function: FSObject::GetName Description: Retrieves the file system object's name string. Arguments: pszBuffer - Address of buffer to accept name string. cchBuffer - Size of destination buffer in characters. Returns: NOERROR - Success. ERROR_INSUFFICIENT_BUFFER - Destination buffer is too small for name. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/24/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT FSObject::GetName(LPTSTR pszBuffer, ULONG cchBuffer) const { HRESULT hr = NOERROR; DBGASSERT((NULL != pszBuffer)); if ((ULONG)m_strFSObjName.Length() < cchBuffer) lstrcpyn(pszBuffer, m_strFSObjName, cchBuffer); else { *pszBuffer = TEXT('\0'); hr = HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER); } return hr; } // // This function was created to fix bug 365936. // Class FSObject has a CPath member who's constructor // can throw CAllocException. Because of this, we // need to isolate this construction operation from // other FSObject code so that we don't try to delete // an FSObject object that has already been destroyed // by the constructor's call stack unwinding process. // HRESULT FSObject_CreateLocalVolume( LPCTSTR pszVolumeName, FSObject **ppObject ) { HRESULT hr = S_OK; *ppObject = NULL; try { FSObject *pNewObject = new FSLocalVolume(pszVolumeName); *ppObject = pNewObject; } catch(CAllocException& e) { hr = E_OUTOFMEMORY; } return hr; } /////////////////////////////////////////////////////////////////////////////// /* Function: FSVolume::~FSVolume Description: Destructor. Closes volume handle. Arguments: None. Returns: Nothing. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/24/96 Initial creation. BrianAu */ /////////////////////////////////////////////////////////////////////////////// FSVolume::~FSVolume( VOID ) { DBGTRACE((DM_CONTROL, DL_MID, TEXT("FSVolume::~FSVolume"))); if (INVALID_HANDLE_VALUE != m_hVolume) CloseHandle(m_hVolume); } /////////////////////////////////////////////////////////////////////////////// /* Function: FSVolume::Initialize Description: Initializes a volume object by opening the NTFS volume. Arguments: dwAccess - Desired access. GENERIC_READ, GENERIC_WRITE. Returns: NOERROR - Success. ERROR_ACCESS_DENIED (hr) - Insufficient access to open device. ERROR_FILE_NOT_FOUND (hr) - Disk device not found. ERROR_INVALID_NAME (hr) - Invalid path string. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/24/96 Initial creation. BrianAu 08/11/96 Added access right handling. BrianAu 08/16/96 Added device name formatting. BrianAu 07/03/97 Changed so caller passes in desired access. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT FSVolume::Initialize( DWORD dwAccess ) { DBGTRACE((DM_CONTROL, DL_MID, TEXT("FSVolume::Initialize"))); DBGPRINT((DM_CONTROL, DL_MID, TEXT("\tdwAccess = 0x%08X"), dwAccess)); HRESULT hr = NOERROR; // // Close the device if it's open. // if (INVALID_HANDLE_VALUE != m_hVolume) CloseHandle(m_hVolume); // // Create a path to the actual quota file on the volume. // This string is appended to the existing "volume name" we already // have. // CPath strQuotaFile(m_strFSObjName); strQuotaFile.AddBackslash(); strQuotaFile += CString("$Extend\\$Quota:$Q:$INDEX_ALLOCATION"); m_hVolume = CreateFile(strQuotaFile, dwAccess, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (INVALID_HANDLE_VALUE == m_hVolume) { // // Couldn't open device because... // 1. I/O error // 2. File (device) not found. // 3. Access denied. // DWORD dwErr = GetLastError(); hr = HRESULT_FROM_WIN32(dwErr); DBGERROR((TEXT("Error %d opening quota file \"%s\""), dwErr, strQuotaFile.Cstr())); } else { // // Save access granted to caller. Will be used to validate // operation requests later. // DBGPRINT((DM_CONTROL, DL_MID, TEXT("Quota file \"%s\" open with access 0x%08X"), strQuotaFile.Cstr(), dwAccess)); m_dwAccessRights = dwAccess; } return hr; } /////////////////////////////////////////////////////////////////////////////// /* Function: FSVolume::QueryObjectQuotaInformation Description: Retrieves quota information for the volume. This includes default quota threshold, default quota limit and system control flags. Arguments: poi - Address of object information buffer. This type contains a subset of the information in FILE_FS_CONTROL_INFORMATION (defined in ntioapi.h). Returns: NOERROR - Success. ERROR_ACCESS_DENIED (hr) - No READ access to quota device. Other - NTFS subsystem failure result. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/24/96 Initial creation. BrianAu 08/11/96 Added access control. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT FSVolume::QueryObjectQuotaInformation( PDISKQUOTA_FSOBJECT_INFORMATION poi ) { DBGTRACE((DM_CONTROL, DL_MID, TEXT("FSVolume::QueryObjectQuotaInformation"))); HRESULT hr = E_FAIL; if (!GrantedAccess(GENERIC_READ)) { DBGPRINT((DM_CONTROL, DL_MID, TEXT("Access denied reading quota info"))); hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); } else { NTSTATUS status = STATUS_SUCCESS; IO_STATUS_BLOCK iosb; FILE_FS_CONTROL_INFORMATION ControlInfo; status = NtQueryVolumeInformationFile( m_hVolume, &iosb, &ControlInfo, sizeof(ControlInfo), FileFsControlInformation); if (STATUS_SUCCESS == status) { // // Update caller's buffer with quota control data. // poi->DefaultQuotaThreshold = ControlInfo.DefaultQuotaThreshold.QuadPart; poi->DefaultQuotaLimit = ControlInfo.DefaultQuotaLimit.QuadPart; poi->FileSystemControlFlags = ControlInfo.FileSystemControlFlags; hr = NOERROR; } else { DBGERROR((TEXT("NtQueryVolumeInformationFile failed with NTSTATUS 0x%08X"), status)); hr = HResultFromNtStatus(status); } } return hr; } /////////////////////////////////////////////////////////////////////////////// /* Function: FSVolume::SetObjectQuotaInformation Description: Writes new quota information to the volume. This includes default quota threshold, default quota limit and system control flags. Arguments: poi - Address of object information buffer. This type contains a subset of the information in FILE_FS_CONTROL_INFORMATION (defined in ntioapi.h). dwChangeMask - Mask specifying which elements in *poi to write to disk. Can be any combination of: FSObject::ChangeState FSObject::ChangeLogFlags FSObject::ChangeThreshold FSObject::ChangeLimit Returns: NOERROR - Success. ERROR_ACCESS_DENIED (hr) - No WRITE access to quota device. Other - NTFS subsystem failure result. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/24/96 Initial creation. BrianAu 08/11/96 Added access control. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT FSVolume::SetObjectQuotaInformation( PDISKQUOTA_FSOBJECT_INFORMATION poi, DWORD dwChangeMask ) const { DBGTRACE((DM_CONTROL, DL_MID, TEXT("FSVolume::SetObjectQuotaInformation"))); HRESULT hr = E_FAIL; if (!GrantedAccess(GENERIC_WRITE)) { DBGPRINT((DM_CONTROL, DL_MID, TEXT("Access denied setting quota info"))); hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); } else { NTSTATUS status = STATUS_SUCCESS; IO_STATUS_BLOCK iosb; FILE_FS_CONTROL_INFORMATION ControlInfo; // // First read current info from disk. // Then replace whatever we're changing. // status = NtQueryVolumeInformationFile( m_hVolume, &iosb, &ControlInfo, sizeof(ControlInfo), FileFsControlInformation); if (STATUS_SUCCESS == status) { // // Only alter those values specified in dwChangeMask. // if (FSObject::ChangeState & dwChangeMask) { ControlInfo.FileSystemControlFlags &= ~DISKQUOTA_STATE_MASK; ControlInfo.FileSystemControlFlags |= (poi->FileSystemControlFlags & DISKQUOTA_STATE_MASK); } if (FSObject::ChangeLogFlags & dwChangeMask) { ControlInfo.FileSystemControlFlags &= ~DISKQUOTA_LOGFLAG_MASK; ControlInfo.FileSystemControlFlags |= (poi->FileSystemControlFlags & DISKQUOTA_LOGFLAG_MASK); } if (FSObject::ChangeThreshold & dwChangeMask) { ControlInfo.DefaultQuotaThreshold.QuadPart = poi->DefaultQuotaThreshold; } if (FSObject::ChangeLimit & dwChangeMask) { ControlInfo.DefaultQuotaLimit.QuadPart = poi->DefaultQuotaLimit; } status = NtSetVolumeInformationFile( m_hVolume, &iosb, &ControlInfo, sizeof(ControlInfo), FileFsControlInformation); if (STATUS_SUCCESS == status) { hr = NOERROR; } else { DBGERROR((TEXT("NtSetVolumeInformationFile failed with NTSTATUS = 0x%08X"), status)); hr = HResultFromNtStatus(status); } } else { DBGERROR((TEXT("NtQueryVolumeInformationFile failed with NTSTATUS = 0x%08X"), status)); hr = HResultFromNtStatus(status); } } return hr; } /////////////////////////////////////////////////////////////////////////////// /* Function: FSVolume::QueryUserQuotaInformation Description: Retrieves user quota information for the volume. This includes quota threshold and quota limit. This function works like an enumerator. Repeated calls will return multiple user records. Arguments: pBuffer - Address of buffer to receive quota information. cbBuffer - Number of bytes in buffer. bReturnSingleEntry - TRUE = Return only one record from quota file. FALSE = Return as many whole entries as possible in buffer. pSidList [optional] - Address of SID list identifying users to obtain information for. Specify NULL to include all users. cbSidList [optional] - Number of bytes in sid list. Ignored if pSidList is NULL. pStartSid [optional] - Address of SID identifying which user is to start the enumeration. Specify NULL to start with current user in enumeration. bRestartScan - TRUE = restart scan from first user in the SID list or the entire file if pSidList is NULL. FALSE = Continue enumeration from current user record. Returns: NOERROR - Success. ERROR_NO_MORE_ITEMS - Read last entry in quota file. ERROR_ACCESS_DENIED (hr) - No READ access to quota device. Other - Quota subsystem error. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/24/96 Initial creation. BrianAu 08/11/96 Added access control. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT FSVolume::QueryUserQuotaInformation( PVOID pBuffer, ULONG cbBuffer, BOOL bReturnSingleEntry, PVOID pSidList, ULONG cbSidList, PSID pStartSid, BOOL bRestartScan ) { DBGTRACE((DM_CONTROL, DL_MID, TEXT("FSVolume::QueryUserQuotaInformation"))); HRESULT hr = E_FAIL; DBGASSERT((NULL != pBuffer)); if (!GrantedAccess(GENERIC_READ)) { DBGPRINT((DM_CONTROL, DL_MID, TEXT("Access denied querying user quota info"))); hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); } else { NTSTATUS status = STATUS_SUCCESS; IO_STATUS_BLOCK iosb; status = NtQueryQuotaInformationFile( m_hVolume, &iosb, pBuffer, cbBuffer, (BOOLEAN)bReturnSingleEntry, pSidList, cbSidList, pStartSid, (BOOLEAN)bRestartScan); switch(status) { case STATUS_SUCCESS: hr = NOERROR; break; default: DBGERROR((TEXT("NtQueryQuotaInformationFile failed with NTSTATUS 0x%08X"), status)); // // Fall through... // case STATUS_NO_MORE_ENTRIES: hr = HResultFromNtStatus(status); break; } } return hr; } /////////////////////////////////////////////////////////////////////////////// /* Function: FSVolume::SetUserQuotaInformation Description: Writes new user quota information to the volume. This includes quota threshold, and quota limit. Arguments: pBuffer - Address of buffer containing quota information. cbBuffer - Number of bytes of data in buffer. Returns: NOERROR - Success. ERROR_ACCESS_DENIED (hr) - No WRITE access to quota device. Or tried to set limit on Administrator. Other - Quota subsystem error. Revision History: Date Description Programmer -------- --------------------------------------------------- ---------- 05/24/96 Initial creation. BrianAu 08/11/96 Added access control. BrianAu */ /////////////////////////////////////////////////////////////////////////////// HRESULT FSVolume::SetUserQuotaInformation( PVOID pBuffer, ULONG cbBuffer ) const { DBGTRACE((DM_CONTROL, DL_MID, TEXT("FSVolume::SetUserQuotaInformation"))); HRESULT hr = NOERROR; DBGASSERT((NULL != pBuffer)); if (!GrantedAccess(GENERIC_WRITE)) { DBGPRINT((DM_CONTROL, DL_MID, TEXT("Access denied setting user quota info"))); hr = HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED); } else { NTSTATUS status = STATUS_SUCCESS; IO_STATUS_BLOCK iosb; status = NtSetQuotaInformationFile( m_hVolume, &iosb, pBuffer, cbBuffer); if (STATUS_SUCCESS == status) { hr = NOERROR; } else { DBGERROR((TEXT("NtSetQuotaInformationFile failed with NTSTATUS 0x%08X"), status)); hr = HResultFromNtStatus(status); } } return hr; } // // Convert an NTSTATUS value to an HRESULT. // This is a simple attempt at converting the most common NTSTATUS values that // might be returned from NtQueryxxxxx and NTSetxxxxxx functions. If I've missed // some obvious ones, go ahead and add them. // HRESULT FSObject::HResultFromNtStatus( NTSTATUS status ) { HRESULT hr = E_FAIL; // Default if none matched. static const struct { NTSTATUS status; HRESULT hr; } rgXref[] = { { STATUS_SUCCESS, NOERROR }, { STATUS_INVALID_PARAMETER, E_INVALIDARG }, { STATUS_NO_MORE_ENTRIES, HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS) }, { STATUS_ACCESS_DENIED, HRESULT_FROM_WIN32(ERROR_ACCESS_DENIED) }, { STATUS_BUFFER_TOO_SMALL, HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER) }, { STATUS_BUFFER_OVERFLOW, HRESULT_FROM_WIN32(ERROR_BUFFER_OVERFLOW) }, { STATUS_INVALID_HANDLE, HRESULT_FROM_WIN32(ERROR_INVALID_HANDLE) }, { STATUS_INVALID_DEVICE_REQUEST, HRESULT_FROM_WIN32(ERROR_BAD_DEVICE) }, { STATUS_FILE_INVALID, HRESULT_FROM_WIN32(ERROR_DEVICE_NOT_AVAILABLE) }}; for (int i = 0; i < ARRAYSIZE(rgXref); i++) { if (rgXref[i].status == status) { hr = rgXref[i].hr; break; } } return hr; }