/* * NTFS Attribute Classes * * Copyright(C) 2010 cyb70289 * * This program/include file is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program/include file is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #ifndef __NTFS_ATTRIBUTE_H_CYB70289 #define __NTFS_ATTRIBUTE_H_CYB70289 //////////////////////////////// // List to hold parsed DataRuns //////////////////////////////// typedef struct tagDataRun_Entry { LONGLONG LCN; // -1 to indicate sparse data ULONGLONG Clusters; ULONGLONG StartVCN; ULONGLONG LastVCN; } DataRun_Entry; typedef class CSList CDataRunList; //////////////////////////////////// // List to hold Index Entry objects //////////////////////////////////// class CIndexEntry; typedef class CSList CIndexEntryList; //////////////////////////////// // Attributes base class //////////////////////////////// class CAttrBase { public: CAttrBase(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr); virtual ~CAttrBase(); protected: const ATTR_HEADER_COMMON *AttrHeader; WORD _SectorSize; DWORD _ClusterSize; DWORD _IndexBlockSize; HANDLE _hVolume; const CFileRecord *FileRecord; public: __inline const ATTR_HEADER_COMMON* GetAttrHeader() const; __inline DWORD GetAttrType() const; __inline DWORD GetAttrTotalSize() const; __inline BOOL IsNonResident() const; __inline WORD GetAttrFlags() const; int GetAttrName(char *buf, DWORD bufLen) const; int GetAttrName(wchar_t *buf, DWORD bufLen) const; __inline BOOL IsUnNamed() const; protected: virtual __inline BOOL IsDataRunOK() const = 0; public: virtual __inline ULONGLONG GetDataSize(ULONGLONG *allocSize = NULL) const = 0; virtual BOOL ReadData(const ULONGLONG &offset, void *bufv, DWORD bufLen, DWORD *actural) const = 0; }; // CAttrBase CAttrBase::CAttrBase(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) { _ASSERT(ahc); _ASSERT(fr); AttrHeader = ahc; FileRecord = fr; _SectorSize = fr->Volume->SectorSize; _ClusterSize = fr->Volume->ClusterSize; _IndexBlockSize = fr->Volume->IndexBlockSize; _hVolume = fr->Volume->hVolume; } CAttrBase::~CAttrBase() { } __inline const ATTR_HEADER_COMMON* CAttrBase::GetAttrHeader() const { return AttrHeader; } __inline DWORD CAttrBase::GetAttrType() const { return AttrHeader->Type; } __inline DWORD CAttrBase::GetAttrTotalSize() const { return AttrHeader->TotalSize; } __inline BOOL CAttrBase::IsNonResident() const { return AttrHeader->NonResident; } __inline WORD CAttrBase::GetAttrFlags() const { return AttrHeader->Flags; } // Get ANSI Attribute name // Return 0: Unnamed, <0: buffer too small, -buffersize, >0 Name length int CAttrBase::GetAttrName(char *buf, DWORD bufLen) const { if (AttrHeader->NameLength) { if (bufLen < AttrHeader->NameLength) return -1*AttrHeader->NameLength; // buffer too small wchar_t *namePtr = (wchar_t*)((BYTE*)AttrHeader + AttrHeader->NameOffset); int len = WideCharToMultiByte(CP_ACP, 0, namePtr, AttrHeader->NameLength, buf, bufLen, NULL, NULL); if (len) { buf[len] = '\0'; NTFS_TRACE1("Attribute name: %s\n", buf); return len; } else { NTFS_TRACE("Unrecognized attribute name or Name buffer too small\n"); return -1*AttrHeader->NameLength; } } else { NTFS_TRACE("Attribute is unnamed\n"); return 0; } } // Get UNICODE Attribute name // Return 0: Unnamed, <0: buffer too small, -buffersize, >0 Name length int CAttrBase::GetAttrName(wchar_t *buf, DWORD bufLen) const { if (AttrHeader->NameLength) { if (bufLen < AttrHeader->NameLength) return -1*AttrHeader->NameLength; // buffer too small bufLen = AttrHeader->NameLength; wchar_t *namePtr = (wchar_t*)((BYTE*)AttrHeader + AttrHeader->NameOffset); wcsncpy(buf, namePtr, bufLen); buf[bufLen] = '\0\0'; NTFS_TRACE("Unicode Attribute Name\n"); return bufLen; } else { NTFS_TRACE("Attribute is unnamed\n"); return 0; } } // Verify if this attribute is unnamed // Useful in analyzing MultiStream files __inline BOOL CAttrBase::IsUnNamed() const { return (AttrHeader->NameLength == 0); } //////////////////////////////// // Resident Attributes //////////////////////////////// class CAttrResident : public CAttrBase { public: CAttrResident(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr); virtual ~CAttrResident(); protected: const ATTR_HEADER_RESIDENT *AttrHeaderR; const void *AttrBody; // Points to Resident Data DWORD AttrBodySize; // Attribute Data Size virtual __inline BOOL IsDataRunOK() const; public: virtual __inline ULONGLONG GetDataSize(ULONGLONG *allocSize = NULL) const; virtual BOOL ReadData(const ULONGLONG &offset, void *bufv, DWORD bufLen, DWORD *actural) const; }; // CAttrResident CAttrResident::CAttrResident(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : CAttrBase(ahc, fr) { AttrHeaderR = (ATTR_HEADER_RESIDENT*)ahc; AttrBody = (void*)((BYTE*)AttrHeaderR + AttrHeaderR->AttrOffset); AttrBodySize = AttrHeaderR->AttrSize; } CAttrResident::~CAttrResident() { } __inline BOOL CAttrResident::IsDataRunOK() const { return TRUE; // Always OK for a resident attribute } // Return Actural Data Size // *allocSize = Allocated Size __inline ULONGLONG CAttrResident::GetDataSize(ULONGLONG *allocSize) const { if (allocSize) *allocSize = AttrBodySize; return (ULONGLONG)AttrBodySize; } // Read "bufLen" bytes from "offset" into "bufv" // Number of bytes acturally read is returned in "*actural" BOOL CAttrResident::ReadData(const ULONGLONG &offset, void *bufv, DWORD bufLen, DWORD *actural) const { _ASSERT(bufv); *actural = 0; if (bufLen == 0) return TRUE; DWORD offsetd = (DWORD)offset; if (offsetd >= AttrBodySize) return FALSE; // offset parameter error if ((offsetd + bufLen) > AttrBodySize) *actural = AttrBodySize - offsetd; // Beyond scope else *actural = bufLen; memcpy(bufv, (BYTE*)AttrBody + offsetd, *actural); return TRUE; } //////////////////////////////// // NonResident Attributes //////////////////////////////// class CAttrNonResident : public CAttrBase { public: CAttrNonResident(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr); virtual ~CAttrNonResident(); protected: const ATTR_HEADER_NON_RESIDENT *AttrHeaderNR; CDataRunList DataRunList; private: BOOL bDataRunOK; BYTE *UnalignedBuf; // Buffer to hold not cluster aligned data BOOL PickData(const BYTE **dataRun, LONGLONG *length, LONGLONG *LCNOffset); BOOL ParseDataRun(); BOOL ReadClusters(void *buf, DWORD clusters, LONGLONG lcn); BOOL ReadVirtualClusters(ULONGLONG vcn, DWORD clusters, void *bufv, DWORD bufLen, DWORD *actural); protected: virtual __inline BOOL IsDataRunOK() const; public: virtual __inline ULONGLONG GetDataSize(ULONGLONG *allocSize = NULL) const; virtual BOOL ReadData(const ULONGLONG &offset, void *bufv, DWORD bufLen, DWORD *actural) const; }; // CAttrNonResident CAttrNonResident::CAttrNonResident(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : CAttrBase(ahc, fr) { AttrHeaderNR = (ATTR_HEADER_NON_RESIDENT*)ahc; UnalignedBuf = new BYTE[_ClusterSize]; bDataRunOK = ParseDataRun(); } CAttrNonResident::~CAttrNonResident() { delete UnalignedBuf; DataRunList.RemoveAll(); } // Parse a single DataRun unit BOOL CAttrNonResident::PickData(const BYTE **dataRun, LONGLONG *length, LONGLONG *LCNOffset) { BYTE size = **dataRun; (*dataRun)++; int lengthBytes = size & 0x0F; int offsetBytes = size >> 4; if (lengthBytes > 8 || offsetBytes > 8) { NTFS_TRACE1("DataRun decode error 1: 0x%02X\n", size); return FALSE; } *length = 0; memcpy(length, *dataRun, lengthBytes); if (*length < 0) { NTFS_TRACE1("DataRun length error: %I64d\n", *length); return FALSE; } (*dataRun) += lengthBytes; *LCNOffset = 0; if (offsetBytes) // Not Sparse File { if ((*dataRun)[offsetBytes-1] & 0x80) *LCNOffset = -1; memcpy(LCNOffset, *dataRun, offsetBytes); (*dataRun) += offsetBytes; } return TRUE; } // Travers DataRun and insert into a link list BOOL CAttrNonResident::ParseDataRun() { NTFS_TRACE("Parsing Non Resident DataRun\n"); NTFS_TRACE2("Start VCN = %I64u, End VCN = %I64u\n", AttrHeaderNR->StartVCN, AttrHeaderNR->LastVCN); const BYTE *dataRun = (BYTE*)AttrHeaderNR + AttrHeaderNR->DataRunOffset; LONGLONG length; LONGLONG LCNOffset; LONGLONG LCN = 0; ULONGLONG VCN = 0; while (*dataRun) { if (PickData(&dataRun, &length, &LCNOffset)) { LCN += LCNOffset; if (LCN < 0) { NTFS_TRACE("DataRun decode error 2\n"); return FALSE; } NTFS_TRACE2("Data length = %I64d clusters, LCN = %I64d", length, LCN); NTFS_TRACE(LCNOffset == 0 ? ", Sparse Data\n" : "\n"); // Store LCN, Data size (clusters) into list DataRun_Entry *dr = new DataRun_Entry; dr->LCN = (LCNOffset == 0) ? -1 : LCN; dr->Clusters = length; dr->StartVCN = VCN; VCN += length; dr->LastVCN = VCN - 1; if (dr->LastVCN <= (AttrHeaderNR->LastVCN - AttrHeaderNR->StartVCN)) { DataRunList.InsertEntry(dr); } else { NTFS_TRACE("DataRun decode error: VCN exceeds bound\n"); // Remove entries DataRunList.RemoveAll(); return FALSE; } } else break; } return TRUE; } // Read clusters from disk, or sparse data // *actural = Clusters acturally read BOOL CAttrNonResident::ReadClusters(void *buf, DWORD clusters, LONGLONG lcn) { if (lcn == -1) // sparse data { NTFS_TRACE("Sparse Data, Fill the buffer with 0\n"); // Fill the buffer with 0 memset(buf, 0, clusters * _ClusterSize); return TRUE; } LARGE_INTEGER addr; DWORD len; addr.QuadPart = lcn * _ClusterSize; len = SetFilePointer(_hVolume, addr.LowPart, &addr.HighPart, FILE_BEGIN); if (len == (DWORD)-1 && GetLastError() != NO_ERROR) { NTFS_TRACE1("Cannot locate cluster with LCN %I64d\n", lcn); } else { if (ReadFile(_hVolume, buf, clusters*_ClusterSize, &len, NULL) && len == clusters*_ClusterSize) { NTFS_TRACE2("Successfully read %u clusters from LCN %I64d\n", clusters, lcn); return TRUE; } else { NTFS_TRACE1("Cannot read cluster with LCN %I64d\n", lcn); } } return FALSE; } // Read Data, cluster based // clusterNo: Begnning cluster Number // clusters: Clusters to read // bufv, bufLen: Returned data // *actural = Number of bytes acturally read BOOL CAttrNonResident::ReadVirtualClusters(ULONGLONG vcn, DWORD clusters, void *bufv, DWORD bufLen, DWORD *actural) { _ASSERT(bufv); _ASSERT(clusters); *actural = 0; BYTE *buf = (BYTE*)bufv; // Verify if clusters exceeds DataRun bounds if (vcn + clusters > (AttrHeaderNR->LastVCN - AttrHeaderNR->StartVCN +1)) { NTFS_TRACE("Cluster exceeds DataRun bounds\n"); return FALSE; } // Verify buffer size if (bufLen < clusters*_ClusterSize) { NTFS_TRACE("Buffer size too small\n"); return FALSE; } // Traverse the DataRun List to find the according LCN const DataRun_Entry *dr = DataRunList.FindFirstEntry(); while(dr) { if (vcn>=dr->StartVCN && vcn<=dr->LastVCN) { DWORD clustersToRead; ULONGLONG vcns = dr->LastVCN - vcn + 1; // Clusters from read pointer to the end if ((ULONGLONG)clusters > vcns) // Fragmented data, we must go on clustersToRead = (DWORD)vcns; else clustersToRead = clusters; if (ReadClusters(buf, clustersToRead, dr->LCN+(vcn-dr->StartVCN))) { buf += clustersToRead*_ClusterSize; clusters -= clustersToRead; *actural += clustersToRead; vcn += clustersToRead; } else break; if (clusters == 0) break; } dr = DataRunList.FindNextEntry(); } *actural *= _ClusterSize; return TRUE; } // Judge if the DataRun is successfully parsed __inline BOOL CAttrNonResident::IsDataRunOK() const { return bDataRunOK; } // Return Actural Data Size // *allocSize = Allocated Size __inline ULONGLONG CAttrNonResident::GetDataSize(ULONGLONG *allocSize) const { if (allocSize) *allocSize = AttrHeaderNR->AllocSize; return AttrHeaderNR->RealSize; } // Read "bufLen" bytes from "offset" into "bufv" // Number of bytes acturally read is returned in "*actural" BOOL CAttrNonResident::ReadData(const ULONGLONG &offset, void *bufv, DWORD bufLen, DWORD *actural) const { // Hard disks can only be accessed by sectors // To be simple and efficient, only implemented cluster based accessing // So cluster unaligned data address should be processed carefully here _ASSERT(bufv); *actural = 0; if (bufLen == 0) return TRUE; // Bounds check if (offset > AttrHeaderNR->RealSize) return FALSE; if ((offset + bufLen) > AttrHeaderNR->RealSize) bufLen = (DWORD)(AttrHeaderNR->RealSize - offset); DWORD len; BYTE *buf = (BYTE*)bufv; // First cluster Number ULONGLONG startVCN = offset / _ClusterSize; // Bytes in first cluster DWORD startBytes = _ClusterSize - (DWORD)(offset % _ClusterSize); // Read first cluster if (startBytes != _ClusterSize) { // First cluster, Unaligned if (((CAttrNonResident*)this)->ReadVirtualClusters(startVCN, 1, UnalignedBuf, _ClusterSize, &len) && len == _ClusterSize) { len = (startBytes < bufLen) ? startBytes : bufLen; memcpy(buf, UnalignedBuf + _ClusterSize - startBytes, len); buf += len; bufLen -= len; *actural += len; startVCN++; } else return FALSE; } if (bufLen == 0) return TRUE; DWORD alignedClusters = bufLen / _ClusterSize; if (alignedClusters) { // Aligned clusters DWORD alignedSize = alignedClusters*_ClusterSize; if (((CAttrNonResident*)this)->ReadVirtualClusters(startVCN, alignedClusters, buf, alignedSize, &len) && len == alignedSize) { startVCN += alignedClusters; buf += alignedSize; bufLen %= _ClusterSize; *actural += len; if (bufLen == 0) return TRUE; } else return FALSE; } // Last cluster, Unaligned if (((CAttrNonResident*)this)->ReadVirtualClusters(startVCN, 1, UnalignedBuf, _ClusterSize, &len) && len == _ClusterSize) { memcpy(buf, UnalignedBuf, bufLen); *actural += bufLen; return TRUE; } else return FALSE; } /////////////////////////////////// // Attribute: Standard Information /////////////////////////////////// class CAttr_StdInfo : public CAttrResident { public: CAttr_StdInfo(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr); virtual ~CAttr_StdInfo(); private: const ATTR_STANDARD_INFORMATION *StdInfo; public: void GetFileTime(FILETIME *writeTm, FILETIME *createTm = NULL, FILETIME *accessTm = NULL) const; __inline DWORD GetFilePermission() const; __inline BOOL IsReadOnly() const; __inline BOOL IsHidden() const; __inline BOOL IsSystem() const; __inline BOOL IsCompressed() const; __inline BOOL IsEncrypted() const; __inline BOOL IsSparse() const; static void UTC2Local(const ULONGLONG &ultm, FILETIME *lftm); }; // CAttr_StdInfo CAttr_StdInfo::CAttr_StdInfo(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : CAttrResident(ahc, fr) { NTFS_TRACE("Attribute: Standard Information\n"); StdInfo = (ATTR_STANDARD_INFORMATION*)AttrBody; } CAttr_StdInfo::~CAttr_StdInfo() { NTFS_TRACE("CAttr_StdInfo deleted\n"); } // Change from UTC time to local time void CAttr_StdInfo::GetFileTime(FILETIME *writeTm, FILETIME *createTm, FILETIME *accessTm) const { UTC2Local(StdInfo->AlterTime, writeTm); if (createTm) UTC2Local(StdInfo->CreateTime, createTm); if (accessTm) UTC2Local(StdInfo->ReadTime, accessTm); } __inline DWORD CAttr_StdInfo::GetFilePermission() const { return StdInfo->Permission; } __inline BOOL CAttr_StdInfo::IsReadOnly() const { return ((StdInfo->Permission) & ATTR_STDINFO_PERMISSION_READONLY); } __inline BOOL CAttr_StdInfo::IsHidden() const { return ((StdInfo->Permission) & ATTR_STDINFO_PERMISSION_HIDDEN); } __inline BOOL CAttr_StdInfo::IsSystem() const { return ((StdInfo->Permission) & ATTR_STDINFO_PERMISSION_SYSTEM); } __inline BOOL CAttr_StdInfo::IsCompressed() const { return ((StdInfo->Permission) & ATTR_STDINFO_PERMISSION_COMPRESSED); } __inline BOOL CAttr_StdInfo::IsEncrypted() const { return ((StdInfo->Permission) & ATTR_STDINFO_PERMISSION_ENCRYPTED); } __inline BOOL CAttr_StdInfo::IsSparse() const { return ((StdInfo->Permission) & ATTR_STDINFO_PERMISSION_SPARSE); } // UTC filetime to Local filetime void CAttr_StdInfo::UTC2Local(const ULONGLONG &ultm, FILETIME *lftm) { LARGE_INTEGER fti; FILETIME ftt; fti.QuadPart = ultm; ftt.dwHighDateTime = fti.HighPart; ftt.dwLowDateTime = fti.LowPart; if (!FileTimeToLocalFileTime(&ftt, lftm)) *lftm = ftt; } //////////////////////////////////////// // FileName helper class // used by FileName and IndexEntry //////////////////////////////////////// class CFileName { public: CFileName(ATTR_FILE_NAME *fn = NULL); virtual ~CFileName(); protected: const ATTR_FILE_NAME *FileName; // May be NULL for an IndexEntry wchar_t *FileNameWUC; // Uppercase Unicode File Name, used to compare file names int FileNameLength; BOOL IsCopy; __inline void SetFileName(ATTR_FILE_NAME *fn); void CFileName::CopyFileName(const CFileName *fn, const ATTR_FILE_NAME *afn); private: void GetFileNameWUC(); public: int Compare(const wchar_t *fn) const; int Compare(const char *fn) const; __inline ULONGLONG GetFileSize() const; __inline DWORD GetFilePermission() const; __inline BOOL IsReadOnly() const; __inline BOOL IsHidden() const; __inline BOOL IsSystem() const; __inline BOOL IsDirectory() const; __inline BOOL IsCompressed() const; __inline BOOL IsEncrypted() const; __inline BOOL IsSparse() const; int GetFileName(char *buf, DWORD bufLen) const; int GetFileName(wchar_t *buf, DWORD bufLen) const; __inline BOOL HasName() const; __inline BOOL IsWin32Name() const; void GetFileTime(FILETIME *writeTm, FILETIME *createTm = NULL, FILETIME *accessTm = NULL) const; }; // CFileName CFileName::CFileName(ATTR_FILE_NAME *fn) { IsCopy = FALSE; FileName = fn; FileNameWUC = NULL; FileNameLength = 0; if (fn) GetFileNameWUC(); } CFileName::~CFileName() { if (FileNameWUC) delete FileNameWUC; } __inline void CFileName::SetFileName(ATTR_FILE_NAME *fn) { FileName = fn; GetFileNameWUC(); } // Copy pointer buffers void CFileName::CopyFileName(const CFileName *fn, const ATTR_FILE_NAME *afn) { if (!IsCopy) { NTFS_TRACE("Cannot call this routine\n"); return; } _ASSERT(fn && afn); NTFS_TRACE("FileName Copied\n"); if (FileNameWUC) delete FileNameWUC; FileNameLength = fn->FileNameLength; FileName = afn; if (fn->FileNameWUC) { FileNameWUC = new wchar_t[FileNameLength+1]; wcsncpy(FileNameWUC, fn->FileNameWUC, FileNameLength); FileNameWUC[FileNameLength] = wchar_t('\0'); } else FileNameWUC = NULL; } // Get uppercase unicode filename and store it in a buffer void CFileName::GetFileNameWUC() { #ifdef _DEBUG char fna[MAX_PATH]; GetFileName(fna, MAX_PATH); // Just show filename in debug window #endif if (FileNameWUC) { delete FileNameWUC; FileNameWUC = NULL; FileNameLength = 0; } wchar_t fns[MAX_PATH]; FileNameLength = GetFileName(fns, MAX_PATH); if (FileNameLength > 0) { FileNameWUC = new wchar_t[FileNameLength+1]; for (int i=0; i MAX_PATH) return 1; // Assume bigger wchar_t fns[MAX_PATH]; for (int i=0; iRealSize : 0; } __inline DWORD CFileName::GetFilePermission() const { return FileName ? FileName->Flags : 0; } __inline BOOL CFileName::IsReadOnly() const { return FileName ? ((FileName->Flags) & ATTR_FILENAME_FLAG_READONLY) : FALSE; } __inline BOOL CFileName::IsHidden() const { return FileName ? ((FileName->Flags) & ATTR_FILENAME_FLAG_HIDDEN) : FALSE; } __inline BOOL CFileName::IsSystem() const { return FileName ? ((FileName->Flags) & ATTR_FILENAME_FLAG_SYSTEM) : FALSE; } __inline BOOL CFileName::IsDirectory() const { return FileName ? ((FileName->Flags) & ATTR_FILENAME_FLAG_DIRECTORY) : FALSE; } __inline BOOL CFileName::IsCompressed() const { return FileName ? ((FileName->Flags) & ATTR_FILENAME_FLAG_COMPRESSED) : FALSE; } __inline BOOL CFileName::IsEncrypted() const { return FileName ? ((FileName->Flags) & ATTR_FILENAME_FLAG_ENCRYPTED) : FALSE; } __inline BOOL CFileName::IsSparse() const { return FileName ? ((FileName->Flags) & ATTR_FILENAME_FLAG_SPARSE) : FALSE; } // Get ANSI File Name // Return 0: Unnamed, <0: buffer too small, -buffersize, >0 Name length int CFileName::GetFileName(char *buf, DWORD bufLen) const { if (FileName == NULL) return 0; int len = 0; if (FileName->NameLength) { if (bufLen < FileName->NameLength) return -1*FileName->NameLength; // buffer too small len = WideCharToMultiByte(CP_ACP, 0, (wchar_t*)FileName->Name, FileName->NameLength, buf, bufLen, NULL, NULL); if (len) { buf[len] = '\0'; NTFS_TRACE1("File Name: %s\n", buf); NTFS_TRACE4("File Permission: %s\t%c%c%c\n", IsDirectory()?"Directory":"File", IsReadOnly()?'R':' ', IsHidden()?'H':' ', IsSystem()?'S':' '); } else { NTFS_TRACE("Unrecognized File Name or FileName buffer too small\n"); } } return len; } // Get Unicode File Name // Return 0: Unnamed, <0: buffer too small, -buffersize, >0 Name length int CFileName::GetFileName(wchar_t *buf, DWORD bufLen) const { if (FileName == NULL) return 0; if (FileName->NameLength) { if (bufLen < FileName->NameLength) return -1*FileName->NameLength; // buffer too small bufLen = FileName->NameLength; wcsncpy(buf, (wchar_t*)FileName->Name, bufLen); buf[bufLen] = wchar_t('\0'); return bufLen; } return 0; } __inline BOOL CFileName::HasName() const { return FileNameLength > 0; } __inline BOOL CFileName::IsWin32Name() const { if (FileName == NULL || FileNameLength <= 0) return FALSE; return (FileName->NameSpace != ATTR_FILENAME_NAMESPACE_DOS); // POSIX, WIN32, WIN32_DOS } // Change from UTC time to local time void CFileName::GetFileTime(FILETIME *writeTm, FILETIME *createTm, FILETIME *accessTm) const { CAttr_StdInfo::UTC2Local(FileName ? FileName->AlterTime : 0, writeTm); if (createTm) CAttr_StdInfo::UTC2Local(FileName ? FileName->CreateTime : 0, createTm); if (accessTm) CAttr_StdInfo::UTC2Local(FileName ? FileName->ReadTime : 0, accessTm); } //////////////////////////////// // Attribute: File Name //////////////////////////////// class CAttr_FileName : public CAttrResident, public CFileName { public: CAttr_FileName(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : CAttrResident(ahc, fr) { NTFS_TRACE("Attribute: File Name\n"); SetFileName((ATTR_FILE_NAME*)AttrBody); } virtual ~CAttr_FileName() { NTFS_TRACE("CAttr_FileName deleted\n"); } private: // File permission and time in $FILE_NAME only updates when the filename changes // So hide these functions to prevent user from getting the error information // Standard Information and IndexEntry keeps the most recent file time and permission infomation void GetFileTime(FILETIME *writeTm, FILETIME *createTm = NULL, FILETIME *accessTm = NULL) const {} __inline DWORD GetFilePermission(){} __inline BOOL IsReadOnly() const {} __inline BOOL IsHidden() const {} __inline BOOL IsSystem() const {} __inline BOOL IsCompressed() const {} __inline BOOL IsEncrypted() const {} __inline BOOL IsSparse() const {} }; // CAttr_FileName ////////////////////////////////// // Attribute: Volume Information ////////////////////////////////// class CAttr_VolInfo : public CAttrResident { public: CAttr_VolInfo(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : CAttrResident(ahc, fr) { NTFS_TRACE("Attribute: Volume Information\n"); VolInfo = (ATTR_VOLUME_INFORMATION*)AttrBody; } virtual ~CAttr_VolInfo() { NTFS_TRACE("CAttr_VolInfo deleted\n"); } private: const ATTR_VOLUME_INFORMATION *VolInfo; public: // Get NTFS Volume Version __inline WORD GetVersion() { return MAKEWORD(VolInfo->MinorVersion, VolInfo->MajorVersion); } }; // CAttr_VolInfo /////////////////////////// // Attribute: Volume Name /////////////////////////// class CAttr_VolName : public CAttrResident { public: CAttr_VolName(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : CAttrResident(ahc, fr) { NTFS_TRACE("Attribute: Volume Name\n"); NameLength = AttrBodySize >> 1; VolNameU = new wchar_t[NameLength+1]; VolNameA = new char[NameLength+1]; memcpy(VolNameU, AttrBody, AttrBodySize); VolNameU[NameLength] = wchar_t('\0'); int len = WideCharToMultiByte(CP_ACP, 0, VolNameU, NameLength, VolNameA, NameLength, NULL, NULL); VolNameA[NameLength] = '\0'; } virtual ~CAttr_VolName() { NTFS_TRACE("CAttr_VolName deleted\n"); delete VolNameU; delete VolNameA; } private: wchar_t *VolNameU; char *VolNameA; DWORD NameLength; public: // Get NTFS Volume Unicode Name __inline int GetName(wchar_t *buf, DWORD len) const { if (len < NameLength) return -1*NameLength; // buffer too small wcsncpy(buf, VolNameU, NameLength+1); return NameLength; } // ANSI Name __inline int GetName(char *buf, DWORD len) const { if (len < NameLength) return -1*NameLength; // buffer too small strncpy(buf, VolNameA, NameLength+1); return NameLength; } }; // CAttr_VolInfo ///////////////////////////////////// // Attribute: Data ///////////////////////////////////// template class CAttr_Data : public TYPE_RESIDENT { public: CAttr_Data(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : TYPE_RESIDENT(ahc, fr) { NTFS_TRACE1("Attribute: Data (%sResident)\n", IsNonResident() ? "Non" : ""); } virtual ~CAttr_Data() { NTFS_TRACE("CAttr_Data deleted\n"); } }; // CAttr_Data ///////////////////////////// // Index Entry helper class ///////////////////////////// class CIndexEntry : public CFileName { public: CIndexEntry() { NTFS_TRACE("Index Entry\n"); IsDefault = TRUE; IndexEntry = NULL; SetFileName(NULL); } CIndexEntry(const INDEX_ENTRY *ie) { NTFS_TRACE("Index Entry\n"); IsDefault = FALSE; _ASSERT(ie); IndexEntry = ie; if (IsSubNodePtr()) { NTFS_TRACE("Points to sub-node\n"); } if (ie->StreamSize) { SetFileName((ATTR_FILE_NAME*)(ie->Stream)); } else { NTFS_TRACE("No FileName stream found\n"); } } virtual ~CIndexEntry() { // Never touch *IndexEntry here if IsCopy == FALSE ! // As the memory have been deallocated by ~CIndexBlock() if (IsCopy && IndexEntry) delete (void*)IndexEntry; NTFS_TRACE("CIndexEntry deleted\n"); } private: BOOL IsDefault; protected: const INDEX_ENTRY *IndexEntry; public: // Use with caution ! CIndexEntry& operator = (const CIndexEntry &ieClass) { if (!IsDefault) { NTFS_TRACE("Cannot call this routine\n"); return *this; } NTFS_TRACE("Index Entry Copied\n"); IsCopy = TRUE; if (IndexEntry) { delete (void*)IndexEntry; IndexEntry = NULL; } const INDEX_ENTRY *ie = ieClass.IndexEntry; _ASSERT(ie && (ie->Size > 0)); IndexEntry = (INDEX_ENTRY*)new BYTE[ie->Size]; memcpy((void*)IndexEntry, ie, ie->Size); CopyFileName(&ieClass, (ATTR_FILE_NAME*)(IndexEntry->Stream)); return *this; } __inline ULONGLONG GetFileReference() const { if (IndexEntry) return IndexEntry->FileReference & 0x0000FFFFFFFFFFFFUL; else return (ULONGLONG)-1; } __inline BOOL IsSubNodePtr() const { if (IndexEntry) return (IndexEntry->Flags & INDEX_ENTRY_FLAG_SUBNODE); else return FALSE; } __inline ULONGLONG GetSubNodeVCN() const { if (IndexEntry) return *(ULONGLONG*)((BYTE*)IndexEntry + IndexEntry->Size - 8); else return (ULONGLONG)-1; } }; // CIndexEntry /////////////////////////////// // Index Block helper class /////////////////////////////// class CIndexBlock : public CIndexEntryList { public: CIndexBlock() { NTFS_TRACE("Index Block\n"); IndexBlock = NULL; } virtual ~CIndexBlock() { NTFS_TRACE("IndexBlock deleted\n"); if (IndexBlock) delete IndexBlock; } private: INDEX_BLOCK *IndexBlock; public: INDEX_BLOCK *AllocIndexBlock(DWORD size) { // Free previous data if any if (GetCount() > 0) RemoveAll(); if (IndexBlock) delete IndexBlock; IndexBlock = (INDEX_BLOCK*)new BYTE[size]; return IndexBlock; } }; // CIndexBlock ///////////////////////////////////// // Attribute: Index Root (Resident) ///////////////////////////////////// class CAttr_IndexRoot : public CAttrResident, public CIndexEntryList { public: CAttr_IndexRoot(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr); virtual ~CAttr_IndexRoot(); private: const ATTR_INDEX_ROOT *IndexRoot; void ParseIndexEntries(); public: __inline BOOL IsFileName() const; }; // CAttr_IndexRoot CAttr_IndexRoot::CAttr_IndexRoot(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr): CAttrResident(ahc, fr) { NTFS_TRACE("Attribute: Index Root\n"); IndexRoot = (ATTR_INDEX_ROOT*)AttrBody; if (IsFileName()) { ParseIndexEntries(); } else { NTFS_TRACE("Index View not supported\n"); } } CAttr_IndexRoot::~CAttr_IndexRoot() { NTFS_TRACE("CAttr_IndexRoot deleted\n"); } // Get all the index entries void CAttr_IndexRoot::ParseIndexEntries() { INDEX_ENTRY *ie; ie = (INDEX_ENTRY*)((BYTE*)(&(IndexRoot->EntryOffset)) + IndexRoot->EntryOffset); DWORD ieTotal = ie->Size; while (ieTotal <= IndexRoot->TotalEntrySize) { CIndexEntry *ieClass = new CIndexEntry(ie); InsertEntry(ieClass); if (ie->Flags & INDEX_ENTRY_FLAG_LAST) { NTFS_TRACE("Last Index Entry\n"); break; } ie = (INDEX_ENTRY*)((BYTE*)ie + ie->Size); // Pick next ieTotal += ie->Size; } } // Check if this IndexRoot contains FileName or IndexView __inline BOOL CAttr_IndexRoot::IsFileName() const { return (IndexRoot->AttrType == ATTR_TYPE_FILE_NAME); } ///////////////////////////////////////////// // Attribute: Index Allocation (NonResident) ///////////////////////////////////////////// class CAttr_IndexAlloc : public CAttrNonResident { public: CAttr_IndexAlloc(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr); virtual ~CAttr_IndexAlloc(); private: ULONGLONG IndexBlockCount; BOOL PatchUS(WORD *sector, int sectors, WORD usn, WORD *usarray); public: __inline ULONGLONG GetIndexBlockCount(); BOOL ParseIndexBlock(const ULONGLONG &vcn, CIndexBlock &ibClass); }; // CAttr_IndexAlloc CAttr_IndexAlloc::CAttr_IndexAlloc(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : CAttrNonResident(ahc, fr) { NTFS_TRACE("Attribute: Index Allocation\n"); IndexBlockCount = 0; if (IsDataRunOK()) { // Get total number of Index Blocks ULONGLONG ibTotalSize; ibTotalSize = GetDataSize(); if (ibTotalSize % _IndexBlockSize) { NTFS_TRACE2("Cannot calulate number of IndexBlocks, total size = %I64u, unit = %u\n", ibTotalSize, _IndexBlockSize); return; } IndexBlockCount = ibTotalSize / _IndexBlockSize; } else { NTFS_TRACE("Index Allocation DataRun parse error\n"); } } CAttr_IndexAlloc::~CAttr_IndexAlloc() { NTFS_TRACE("CAttr_IndexAlloc deleted\n"); } // Verify US and update sectors BOOL CAttr_IndexAlloc::PatchUS(WORD *sector, int sectors, WORD usn, WORD *usarray) { int i; for (i=0; i>1) - 1); if (*sector != usn) return FALSE; // USN error *sector = usarray[i]; // Write back correct data sector++; } return TRUE; } __inline ULONGLONG CAttr_IndexAlloc::GetIndexBlockCount() { return IndexBlockCount; } // Parse a single Index Block // vcn = Index Block VCN in Index Allocation Data Attributes // ibClass holds the parsed Index Entries BOOL CAttr_IndexAlloc::ParseIndexBlock(const ULONGLONG &vcn, CIndexBlock &ibClass) { if (vcn >= IndexBlockCount) // Bounds check return FALSE; // Allocate buffer for a single Index Block INDEX_BLOCK *ibBuf = ibClass.AllocIndexBlock(_IndexBlockSize); // Sectors Per Index Block DWORD sectors = _IndexBlockSize / _SectorSize; // Read one Index Block DWORD len; if (ReadData(vcn*_IndexBlockSize, ibBuf, _IndexBlockSize, &len) && len == _IndexBlockSize) { if (ibBuf->Magic != INDEX_BLOCK_MAGIC) { NTFS_TRACE("Index Block parse error: Magic mismatch\n"); return FALSE; } // Patch US WORD *usnaddr = (WORD*)((BYTE*)ibBuf + ibBuf->OffsetOfUS); WORD usn = *usnaddr; WORD *usarray = usnaddr + 1; if (!PatchUS((WORD*)ibBuf, sectors, usn, usarray)) { NTFS_TRACE("Index Block parse error: Update Sequence Number\n"); return FALSE; } INDEX_ENTRY *ie; ie = (INDEX_ENTRY*)((BYTE*)(&(ibBuf->EntryOffset)) + ibBuf->EntryOffset); DWORD ieTotal = ie->Size; while (ieTotal <= ibBuf->TotalEntrySize) { CIndexEntry *ieClass = new CIndexEntry(ie); ibClass.InsertEntry(ieClass); if (ie->Flags & INDEX_ENTRY_FLAG_LAST) { NTFS_TRACE("Last Index Entry\n"); break; } ie = (INDEX_ENTRY*)((BYTE*)ie + ie->Size); // Pick next ieTotal += ie->Size; } return TRUE; } else return FALSE; } //////////////////////////////////////////// // Attribute: Bitmap //////////////////////////////////////////// template class CAttr_Bitmap : public TYPE_RESIDENT { public: CAttr_Bitmap(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr); virtual ~CAttr_Bitmap(); private: ULONGLONG BitmapSize; // Bitmap data size BYTE *BitmapBuf; // Bitmap data buffer LONGLONG CurrentCluster; public: BOOL IsClusterFree(const ULONGLONG &cluster) const; }; // CAttr_Bitmap template CAttr_Bitmap::CAttr_Bitmap(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : TYPE_RESIDENT(ahc, fr) { NTFS_TRACE1("Attribute: Bitmap (%sResident)\n", IsNonResident() ? "Non" : ""); CurrentCluster = -1; if (IsDataRunOK()) { BitmapSize = GetDataSize(); if (IsNonResident()) BitmapBuf = new BYTE[_ClusterSize]; else { BitmapBuf = new BYTE[(DWORD)BitmapSize]; DWORD len; if (!(ReadData(0, BitmapBuf, (DWORD)BitmapSize, &len) && len == (DWORD)BitmapSize)) { BitmapBuf = NULL; NTFS_TRACE("Read Resident Bitmap data failed\n"); } else { NTFS_TRACE1("%u bytes of resident Bitmap data read\n", len); } } } else { BitmapSize = 0; BitmapBuf = 0; } } template CAttr_Bitmap::~CAttr_Bitmap() { if (BitmapBuf) delete BitmapBuf; NTFS_TRACE("CAttr_Bitmap deleted\n"); } // Verify if a single cluster is free template BOOL CAttr_Bitmap::IsClusterFree(const ULONGLONG &cluster) const { if (!IsDataRunOK() || !BitmapBuf) return FALSE; if (IsNonResident()) { LONGLONG idx = (LONGLONG)cluster >> 3; DWORD clusterSize = ((CNTFSVolume*)Volume)->GetClusterSize(); LONGLONG clusterOffset = idx/clusterSize; cluster -= (clusterOffset*clusterSize*8); // Read one cluster of data if buffer mismatch if (CurrentCluster != clusterOffset) { DWORD len; if (ReadData(clusterOffset, BitmapBuf, clusterSize, &len) && len == clusterSize) { CurrentCluster = clusterOffset; } else { CurrentCluster = -1; return FALSE; } } } // All the Bitmap data is already in BitmapBuf DWORD idx = (DWORD)(cluster >> 3); if (IsNonResident() == FALSE) { if (idx >= BitmapSize) return TRUE; // Resident data bounds check error } BYTE fac = (BYTE)(cluster % 8); return ((BitmapBuf[idx] & (1< CFileRecordList; //////////////////////////////////////////// // Attribute: Attribute List //////////////////////////////////////////// template class CAttr_AttrList : public TYPE_RESIDENT { public: CAttr_AttrList(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr); virtual ~CAttr_AttrList(); private: CFileRecordList FileRecordList; }; // CAttr_AttrList template CAttr_AttrList::CAttr_AttrList(const ATTR_HEADER_COMMON *ahc, const CFileRecord *fr) : TYPE_RESIDENT(ahc, fr) { NTFS_TRACE("Attribute: Attribute List\n"); if (fr->FileReference == (ULONGLONG)-1) return; ULONGLONG offset = 0; DWORD len; ATTR_ATTRIBUTE_LIST alRecord; while (ReadData(offset, &alRecord, sizeof(ATTR_ATTRIBUTE_LIST), &len) && len == sizeof(ATTR_ATTRIBUTE_LIST)) { if (ATTR_INDEX(alRecord.AttrType) > ATTR_NUMS) { NTFS_TRACE("Attribute List parse error1\n"); break; } NTFS_TRACE1("Attribute List: 0x%04x\n", alRecord.AttrType); ULONGLONG recordRef = alRecord.BaseRef & 0x0000FFFFFFFFFFFFUL; if (recordRef != fr->FileReference) // Skip contained attributes { DWORD am = ATTR_MASK(alRecord.AttrType); if (am & fr->AttrMask) // Skip unwanted attributes { CFileRecord *frnew = new CFileRecord(fr->Volume); FileRecordList.InsertEntry(frnew); frnew->AttrMask = am; if (!frnew->ParseFileRecord(recordRef)) { NTFS_TRACE("Attribute List parse error2\n"); break; } frnew->ParseAttrs(); // Insert new found AttrList to fr->AttrList const CAttrBase *ab = (CAttrBase*)frnew->FindFirstAttr(alRecord.AttrType); while (ab) { CAttrList *al = (CAttrList*)&fr->AttrList[ATTR_INDEX(alRecord.AttrType)]; al->InsertEntry((CAttrBase*)ab); ab = frnew->FindNextAttr(alRecord.AttrType); } // Throw away frnew->AttrList entries to prevent free twice (fr will delete them) frnew->AttrList[ATTR_INDEX(alRecord.AttrType)].ThrowAll(); } } offset += alRecord.RecordSize; } } template CAttr_AttrList::~CAttr_AttrList() { NTFS_TRACE("CAttr_AttrList deleted\n"); } #endif