8d6a724d21935784ce09d028c9ec44eeef7a92ed
[tpg/acess2.git] / KernelLand / Modules / Filesystems / NTFS / main.c
1 /*
2  * Acess2 - NTFS Driver
3  * By John Hodge (thePowersGang)
4  *
5  * main.c
6  * - Driver core
7  *
8  * Reference: ntfsdoc.pdf
9  */
10 #define DEBUG   0
11 #define VERBOSE 0
12 #include <acess.h>
13 #include <vfs.h>
14 #include "common.h"
15 #include <modules.h>
16 #include <utf16.h>
17
18 // === PROTOTYPES ===
19  int    NTFS_Install(char **Arguments);
20  int    NTFS_Detect(int FD);
21 tVFS_Node       *NTFS_InitDevice(const char *Devices, const char **Options);
22 void    NTFS_Unmount(tVFS_Node *Node);
23 // - MFT Related Functions
24 tNTFS_FILE_Header       *NTFS_GetMFT(tNTFS_Disk *Disk, Uint32 MFTEntry);
25 void    NTFS_ReleaseMFT(tNTFS_Disk *Disk, Uint32 MFTEntry, tNTFS_FILE_Header *Entry);
26 tNTFS_Attrib    *NTFS_GetAttrib(tNTFS_Disk *Disk, Uint32 MFTEntry, int Type, const char *Name, int DesIdx);
27 size_t  NTFS_ReadAttribData(tNTFS_Attrib *Attrib, Uint64 Offset, size_t Length, void *Buffer);
28 void    NTFS_DumpEntry(tNTFS_Disk *Disk, Uint32 Entry);
29
30 // === GLOBALS ===
31 MODULE_DEFINE(0, 0x0A /*v0.1*/, FS_NTFS, NTFS_Install, NULL);
32 tVFS_Driver     gNTFS_FSInfo = {
33         .Name = "ntfs",
34         .Detect = NTFS_Detect,
35         .InitDevice = NTFS_InitDevice,
36         .Unmount = NTFS_Unmount,
37         .GetNodeFromINode = NULL
38 };
39 tVFS_NodeType   gNTFS_DirType = {
40         .TypeName = "NTFS-Dir",
41         .ReadDir = NTFS_ReadDir,
42         .FindDir = NTFS_FindDir,
43         .Close = NULL
44         };
45 tVFS_NodeType   gNTFS_FileType = {
46         .TypeName = "NTFS-File",
47         .Close = NULL
48         };
49
50 tNTFS_Disk      gNTFS_Disks;
51
52 // === CODE ===
53 /**
54  * \brief Installs the NTFS driver
55  */
56 int NTFS_Install(char **Arguments)
57 {
58         VFS_AddDriver( &gNTFS_FSInfo );
59         return 0;
60 }
61
62 /**
63  * \brief Detect if a volume is NTFS
64  */
65 int NTFS_Detect(int FD)
66 {
67         tNTFS_BootSector        bs;
68         VFS_ReadAt(FD, 0, 512, &bs);
69         
70         if( bs.BytesPerSector == 0 || (bs.BytesPerSector & 511) )
71                 return 0;
72
73         Uint64  ncluster = bs.TotalSectorCount / bs.SectorsPerCluster;
74         if( bs.MFTStart >= ncluster || bs.MFTMirrorStart >= ncluster )
75                 return 0;
76
77         if( memcmp(bs.SystemID, "NTFS    ", 8) != 0 )
78                 return 0;
79         
80         return 1;
81 }
82
83 /**
84  * \brief Mount a NTFS volume
85  */
86 tVFS_Node *NTFS_InitDevice(const char *Device, const char **Options)
87 {
88         tNTFS_Disk      *disk;
89         tNTFS_BootSector        bs;
90         
91         disk = calloc( sizeof(tNTFS_Disk), 1 );
92         
93         disk->FD = VFS_Open(Device, VFS_OPENFLAG_READ);
94         if(!disk->FD) {
95                 free(disk);
96                 return NULL;
97         }
98         
99         VFS_ReadAt(disk->FD, 0, 512, &bs);
100
101 #if 0   
102         Log_Debug("FS_NTFS", "Jump = %02x%02x%02x",
103                 bs.Jump[0],
104                 bs.Jump[1],
105                 bs.Jump[2]);
106         Log_Debug("FS_NTFS", "SystemID = %02x%02x%02x%02x%02x%02x%02x%02x (%8C)",
107                 bs.SystemID[0], bs.SystemID[1], bs.SystemID[2], bs.SystemID[3],
108                 bs.SystemID[4], bs.SystemID[5], bs.SystemID[6], bs.SystemID[7],
109                 bs.SystemID
110                 );
111         Log_Debug("FS_NTFS", "BytesPerSector = %i", bs.BytesPerSector);
112         Log_Debug("FS_NTFS", "SectorsPerCluster = %i", bs.SectorsPerCluster);
113         Log_Debug("FS_NTFS", "MediaDescriptor = 0x%x", bs.MediaDescriptor);
114         Log_Debug("FS_NTFS", "SectorsPerTrack = %i", bs.SectorsPerTrack);
115         Log_Debug("FS_NTFS", "Heads = %i", bs.Heads);
116         Log_Debug("FS_NTFS", "TotalSectorCount = 0x%llx", bs.TotalSectorCount);
117         Log_Debug("FS_NTFS", "MFTStart = 0x%llx", bs.MFTStart);
118         Log_Debug("FS_NTFS", "MFTMirrorStart = 0x%llx", bs.MFTMirrorStart);
119         Log_Debug("FS_NTFS", "ClustersPerMFTRecord = %i", bs.ClustersPerMFTRecord);
120         Log_Debug("FS_NTFS", "ClustersPerIndexRecord = %i", bs.ClustersPerIndexRecord);
121         Log_Debug("FS_NTFS", "SerialNumber = 0x%llx", bs.SerialNumber);
122 #endif
123         
124         disk->ClusterSize = bs.BytesPerSector * bs.SectorsPerCluster;
125         disk->MFTBase = bs.MFTStart;
126         Log_Debug("NTFS", "Cluster Size = %i KiB", disk->ClusterSize/1024);
127         Log_Debug("NTFS", "MFT Base = %i", disk->MFTBase);
128         Log_Debug("NTFS", "TotalSectorCount = 0x%x", bs.TotalSectorCount);
129         
130         if( bs.ClustersPerMFTRecord < 0 ) {
131                 disk->MFTRecSize = 1 << (-bs.ClustersPerMFTRecord);
132         }
133         else {
134                 disk->MFTRecSize = bs.ClustersPerMFTRecord * disk->ClusterSize;
135         }
136         //NTFS_DumpEntry(disk, 0);      // $MFT
137         //NTFS_DumpEntry(disk, 3);      // $VOLUME
138
139         disk->InodeCache = Inode_GetHandle();
140         
141         disk->MFTDataAttr = NULL;
142         disk->MFTDataAttr = NTFS_GetAttrib(disk, 0, NTFS_FileAttrib_Data, "", 0);
143         //NTFS_DumpEntry(disk, 5);      // .
144
145         disk->RootDir.I30Root = NTFS_GetAttrib(disk, 5, NTFS_FileAttrib_IndexRoot, "$I30", 0);
146         disk->RootDir.I30Allocation = NTFS_GetAttrib(disk, 5, NTFS_FileAttrib_IndexAllocation, "$I30", 0);
147         disk->RootDir.Node.Inode = 5;   // MFT Ent #5 is filesystem root
148         disk->RootDir.Node.ImplPtr = disk;
149         disk->RootDir.Node.Type = &gNTFS_DirType;
150         disk->RootDir.Node.Flags = VFS_FFLAG_DIRECTORY;
151         
152         disk->RootDir.Node.UID = 0;
153         disk->RootDir.Node.GID = 0;
154         
155         disk->RootDir.Node.NumACLs = 1;
156         disk->RootDir.Node.ACLs = &gVFS_ACL_EveryoneRX;
157
158         #if 0
159         {
160                 // Read from allocation
161                 char buf[disk->ClusterSize];
162                 size_t len = NTFS_ReadAttribData(disk->RootDir.I30Allocation, 0, sizeof(buf), buf);
163                 Debug_HexDump("RootDir allocation", buf, len);
164         }
165         #endif
166
167         return &disk->RootDir.Node;
168 }
169
170 /**
171  * \brief Unmount an NTFS Disk
172  */
173 void NTFS_Unmount(tVFS_Node *Node)
174 {
175         tNTFS_Disk      *Disk = Node->ImplPtr;
176         VFS_Close(Disk->FD);
177         free(Disk);
178 }
179
180 int NTFS_int_ApplyUpdateSequence(void *Buffer, size_t BufLen, const Uint16 *Sequence, size_t NumEntries)
181 {
182         Uint16  cksum = Sequence[0];
183         LOG("cksum = %04x", cksum);
184         Sequence ++;
185         Uint16  *buf16 = Buffer;
186         for( int i = 0; i < NumEntries-1; i ++ )
187         {
188                 size_t  ofs = (i+1)*512 - 2;
189                 if( ofs + 2 > BufLen ) {
190                         // Oops?
191                         Log_Warning("NTFS", "%x > %x", ofs+2, BufLen);
192                 }
193                 Uint16  *cksum_word = &buf16[ofs/2];
194                 LOG("[%i]: %04x => %04x", i, Sequence[i], *cksum_word);
195                 if( *cksum_word != cksum ) {
196                         Log_Warning("NTFS", "Disk corruption detected");
197                         return 1;
198                 }
199                 *cksum_word = Sequence[i];
200         }
201         return 0;
202 }
203
204 tNTFS_FILE_Header *NTFS_GetMFT(tNTFS_Disk *Disk, Uint32 MFTEntry)
205 {
206         tNTFS_FILE_Header       *ret = malloc( Disk->MFTRecSize );
207         if(!ret) {
208                 Log_Warning("FS_NTFS", "malloc() fail!");
209                 return NULL;
210         }
211         
212         // NOTE: The MFT is a file, and can get fragmented
213         if( !Disk->MFTDataAttr ) {
214                 VFS_ReadAt( Disk->FD,
215                         Disk->MFTBase * Disk->ClusterSize + MFTEntry * Disk->MFTRecSize,
216                         Disk->MFTRecSize,
217                         ret);
218         }
219         else {
220                 NTFS_ReadAttribData(Disk->MFTDataAttr, MFTEntry * Disk->MFTRecSize, Disk->MFTRecSize, ret);
221         }
222
223         NTFS_int_ApplyUpdateSequence(ret, Disk->MFTRecSize,
224                 (void*)((char*)ret + ret->UpdateSequenceOfs), ret->UpdateSequenceSize
225                 );
226
227         return ret;
228 }
229
230 void NTFS_ReleaseMFT(tNTFS_Disk *Disk, Uint32 MFTEntry, tNTFS_FILE_Header *Entry)
231 {
232         free(Entry);
233 }
234
235 static inline Uint64 _getVariableLengthInt(const void *Ptr, int Length, int bExtend)
236 {
237         const Uint8     *data = Ptr;
238         Uint64  bits = 0;
239         for( int i = 0; i < Length; i ++ )
240                 bits |= (Uint64)data[i] << (i*8);
241         if( bExtend && Length && data[Length-1] & 0x80 ) {
242                 for( int i = Length; i < 8; i ++ )
243                         bits |= 0xFF << (i*8);
244         }
245         return bits;    // 
246 }
247
248 const void *_GetDataRun(const void *ptr, const void *limit, Uint64 LastLCN, Uint64 *Count, Uint64 *LCN)
249 {
250         // Clean exit?
251         if( ptr == limit ) {
252                 LOG("Clean end of list");
253                 return NULL;
254         }
255         
256         const Uint8     *data = ptr;
257         
258         // Offset size
259         Uint8   ofsSize = data[0] >> 4;
260         Uint8   lenSize = data[0] & 0xF;
261         LOG("ofsSize = %i, lenSize = %i", ofsSize, lenSize);
262         if( ofsSize > 8 )
263                 return NULL;
264         if( lenSize > 8 || lenSize < 1 )
265                 return NULL;
266         if( data + 1 + ofsSize + lenSize > (const Uint8*)limit )
267                 return NULL;
268         
269         if( Count ) {
270                 *Count = _getVariableLengthInt(data + 1, lenSize, 0);
271         }
272         if( LCN ) {
273                 *LCN = LastLCN + (Sint64)_getVariableLengthInt(data + 1 + lenSize, ofsSize, 1);
274         }
275         
276         return data + 1 + ofsSize + lenSize;
277 }
278
279 tNTFS_Attrib *NTFS_GetAttrib(tNTFS_Disk *Disk, Uint32 MFTEntry, int Type, const char *Name, int DesIdx)
280 {
281         ENTER("pDisk xMFTEntry xType sName iDesIdx",
282                 Disk, MFTEntry, Type, Name, DesIdx);
283          int    curIdx = 0;
284         // TODO: Scan cache of attributes
285         
286         // Load MFT entry
287         tNTFS_FILE_Header *hdr = NTFS_GetMFT(Disk, MFTEntry);
288         LOG("hdr = %p", hdr);
289
290         tNTFS_FILE_Attrib       *attr;
291         for( size_t ofs = hdr->FirstAttribOfs; ofs < hdr->RecordSize; ofs += attr->Size )
292         {
293                 attr = (void*)( (tVAddr)hdr + ofs );
294                 // Sanity #1: Type
295                 if( ofs + 4 > hdr->RecordSize )
296                         break ;
297                 // End-of-list?
298                 if( attr->Type == 0xFFFFFFFF )
299                         break;
300                 // Sanity #2: Type,Size
301                 if( ofs + 8 > hdr->RecordSize )
302                         break;
303                 // Sanity #3: Reported size
304                 if( attr->Size < sizeof(attr->Resident) )
305                         break;
306                 // Sanity #4: Reported size fits
307                 if( ofs + attr->Size > hdr->RecordSize )
308                         break;
309                 
310                 // - Chceck if this attribute is the one requested
311                 LOG("Type check %x == %x", attr->Type, Type);
312                 if( attr->Type != Type )
313                         continue;
314                 if( Name ) {
315                         LOG("Name check = '%s'", Name);
316                         const void      *name16 = (char*)attr + attr->NameOffset;
317                         if( UTF16_CompareWithUTF8(attr->NameLength, name16, Name) != 0 )
318                                 continue ;
319                 }
320                 LOG("Idx check %i", curIdx);
321                 if( curIdx++ != DesIdx )
322                         continue ;
323
324                 // - Construct (and cache) attribute description
325                 ASSERT(attr->NameOffset % 1 == 0);
326                 Uint16  *name16 = (Uint16*)attr + attr->NameOffset/2;
327                 size_t  namelen = UTF16_ConvertToUTF8(0, NULL, attr->NameLength, name16);
328                 size_t  edatalen = (attr->NonresidentFlag ? 0 : attr->Resident.AttribLen*4);
329                 tNTFS_Attrib *ret = malloc( sizeof(tNTFS_Attrib) + namelen + 1 + edatalen );
330                 if(!ret) {
331                         LEAVE('n');
332                         return NULL;
333                 }
334                 if( attr->NonresidentFlag )
335                         ret->Name = (void*)(ret + 1);
336                 else {
337                         ret->ResidentData = ret + 1;
338                         ret->Name = (char*)ret->ResidentData + edatalen;
339                 }
340                 
341                 ret->Disk = Disk;
342                 ret->Type = attr->Type;
343                 UTF16_ConvertToUTF8(namelen+1, ret->Name, attr->NameLength, name16);
344                 ret->IsResident = !(attr->NonresidentFlag);
345
346                 LOG("Creating with %x '%s'", ret->Type, ret->Name);
347
348                 if( attr->NonresidentFlag )
349                 {
350                         ret->DataSize = attr->NonResident.RealSize;
351                         ret->NonResident.CompressionUnitL2Size = attr->NonResident.CompressionUnitSize;
352                         ret->NonResident.FirstPopulatedCluster = attr->NonResident.StartingVCN;
353                         // Count data runs
354                         const char *limit = (char*)attr + attr->Size;
355                          int    nruns = 0;
356                         const char *datarun = (char*)attr + attr->NonResident.DataRunOfs;
357                         while( (datarun = _GetDataRun(datarun, limit, 0, NULL, NULL)) )
358                                 nruns ++;
359                         LOG("nruns = %i", nruns);
360                         // Allocate data runs
361                         ret->NonResident.nRuns = nruns;
362                         ret->NonResident.Runs = malloc( sizeof(tNTFS_AttribDataRun) * nruns );
363                          int    i = 0;
364                         datarun = (char*)attr + attr->NonResident.DataRunOfs;
365                         Uint64  lastLCN = 0;
366                         while( datarun && i < nruns )
367                         {
368                                 tNTFS_AttribDataRun     *run = &ret->NonResident.Runs[i];
369                                 datarun = _GetDataRun(datarun,limit, lastLCN, &run->Count, &run->LCN);
370                                 LOG("Run %i: %llx+%llx", i, run->LCN, run->Count);
371                                 lastLCN = run->LCN;
372                                 i ++;
373                         }
374                 }
375                 else
376                 {
377                         ret->DataSize = edatalen;
378                         memcpy(ret->ResidentData, (char*)attr + attr->Resident.AttribOfs, edatalen);
379                 }
380                 
381                 LEAVE('p', ret);
382                 return ret;
383         }
384
385         NTFS_ReleaseMFT(Disk, MFTEntry, hdr);
386         LEAVE('n');
387         return NULL;
388 }
389
390 size_t NTFS_ReadAttribData(tNTFS_Attrib *Attrib, Uint64 Offset, size_t Length, void *Buffer)
391 {
392         if( !Attrib )
393                 return 0;
394         if( Offset >= Attrib->DataSize )
395                 return 0;
396         if( Length > Attrib->DataSize )
397                 Length = Attrib->DataSize;
398         if( Offset + Length > Attrib->DataSize )
399                 Length = Attrib->DataSize - Offset;
400                 
401         if( Attrib->IsResident )
402         {
403                 memcpy(Buffer, Attrib->ResidentData, Length);
404                 return Length;
405         }
406         else
407         {
408                 size_t  ret = 0;
409                 tNTFS_Disk      *Disk = Attrib->Disk;
410                 Uint64  first_cluster = Offset / Disk->ClusterSize;
411                 size_t  cluster_ofs = Offset % Disk->ClusterSize;
412                 if( first_cluster < Attrib->NonResident.FirstPopulatedCluster ) {
413                         Log_Warning("NTFS", "TODO: Ofs < FirstVCN");
414                 }
415                 first_cluster -= Attrib->NonResident.FirstPopulatedCluster;
416                 if( Attrib->NonResident.CompressionUnitL2Size )
417                 {
418                         // TODO: Compression
419                         Log_Warning("NTFS", "Compression unsupported");
420                         // NOTE: Compressed blocks show up in pairs of runs
421                         // - The first contains the compressed data
422                         // - The second is a placeholder 'sparse' (LCN=0) to align to the compression unit
423                 }
424                 else
425                 {
426                         // Iterate through data runs until the desired run is located
427                         for( int i = 0; i < Attrib->NonResident.nRuns && Length; i ++ )
428                         {
429                                 tNTFS_AttribDataRun     *run = &Attrib->NonResident.Runs[i];
430                                 if( first_cluster > run->Count ) {
431                                         first_cluster -= run->Count;
432                                         continue ;
433                                 }
434                                 size_t  avail_bytes = (run->Count-first_cluster)*Disk->ClusterSize - cluster_ofs;
435                                 if( avail_bytes > Length )
436                                         avail_bytes = Length;
437                                 // Read from this extent
438                                 if( run->LCN == 0 ) {
439                                         memset(Buffer, 0, avail_bytes);
440                                 }
441                                 else {
442                                         VFS_ReadAt(Disk->FD,
443                                                 (run->LCN + first_cluster)*Disk->ClusterSize + cluster_ofs,
444                                                 avail_bytes,
445                                                 Buffer
446                                                 );
447                                 }
448                                 Length -= avail_bytes;
449                                 Buffer += avail_bytes;
450                                 ret += avail_bytes;
451                                 first_cluster = 0;
452                                 cluster_ofs = 0;
453                                 continue ;
454                         }
455                 }
456                 return ret;
457         }
458 }
459
460 /**
461  * \brief Dumps a MFT Entry
462  */
463 void NTFS_DumpEntry(tNTFS_Disk *Disk, Uint32 Entry)
464 {
465         tNTFS_FILE_Attrib       *attr;
466          int    i;
467         
468         tNTFS_FILE_Header       *hdr = malloc( Disk->MFTRecSize );
469         if(!hdr) {
470                 Log_Warning("FS_NTFS", "malloc() fail!");
471                 return ;
472         }
473         
474         VFS_ReadAt( Disk->FD,
475                 Disk->MFTBase * Disk->ClusterSize + Entry * Disk->MFTRecSize,
476                 Disk->MFTRecSize,
477                 hdr);
478         
479         Log_Debug("FS_NTFS", "MFT Entry #%i", Entry);
480         Log_Debug("FS_NTFS", "- Magic = 0x%08x (%4C)", hdr->Magic, &hdr->Magic);
481         Log_Debug("FS_NTFS", "- UpdateSequenceOfs = 0x%x", hdr->UpdateSequenceOfs);
482         Log_Debug("FS_NTFS", "- UpdateSequenceSize = 0x%x", hdr->UpdateSequenceSize);
483         Log_Debug("FS_NTFS", "- LSN = 0x%x", hdr->LSN);
484         Log_Debug("FS_NTFS", "- SequenceNumber = %i", hdr->SequenceNumber);
485         Log_Debug("FS_NTFS", "- HardLinkCount = %i", hdr->HardLinkCount);
486         Log_Debug("FS_NTFS", "- FirstAttribOfs = 0x%x", hdr->FirstAttribOfs);
487         Log_Debug("FS_NTFS", "- Flags = 0x%x", hdr->Flags);
488         Log_Debug("FS_NTFS", "- RecordSize = 0x%x", hdr->RecordSize);
489         Log_Debug("FS_NTFS", "- RecordSpace = 0x%x", hdr->RecordSpace);
490         Log_Debug("FS_NTFS", "- Reference = 0x%llx", hdr->Reference);
491         Log_Debug("FS_NTFS", "- NextAttribID = 0x%04x", hdr->NextAttribID);
492         
493         attr = (void*)( (char*)hdr + hdr->FirstAttribOfs );
494         i = 0;
495         while( (tVAddr)attr < (tVAddr)hdr + hdr->RecordSize )
496         {
497                 if(attr->Type == 0xFFFFFFFF)    break;
498                 Log_Debug("FS_NTFS", "- Attribute %i", i ++);
499                 Log_Debug("FS_NTFS", " > Type = 0x%x", attr->Type);
500                 Log_Debug("FS_NTFS", " > Size = 0x%x", attr->Size);
501                 Log_Debug("FS_NTFS", " > ResidentFlag = 0x%x", attr->NonresidentFlag);
502                 Log_Debug("FS_NTFS", " > NameLength = %i", attr->NameLength);
503                 Log_Debug("FS_NTFS", " > NameOffset = 0x%x", attr->NameOffset);
504                 Log_Debug("FS_NTFS", " > Flags = 0x%x", attr->Flags);
505                 Log_Debug("FS_NTFS", " > AttributeID = 0x%x", attr->AttributeID);
506                 {
507                         Uint16  *name16 = (void*)((char*)attr + attr->NameOffset);
508                         size_t  len = UTF16_ConvertToUTF8(0, NULL, attr->NameLength, name16);
509                         char    name[len+1];
510                         UTF16_ConvertToUTF8(len+1, name, attr->NameLength, name16);
511                         Log_Debug("FS_NTFS", " > Name = '%s'", name);
512                 }
513                 if( !attr->NonresidentFlag ) {
514                         Log_Debug("FS_NTFS", " > AttribLen = 0x%x", attr->Resident.AttribLen);
515                         Log_Debug("FS_NTFS", " > AttribOfs = 0x%x", attr->Resident.AttribOfs);
516                         Log_Debug("FS_NTFS", " > IndexedFlag = 0x%x", attr->Resident.IndexedFlag);
517                         Debug_HexDump("FS_NTFS",
518                                 (void*)( (tVAddr)attr + attr->Resident.AttribOfs ),
519                                 attr->Resident.AttribLen
520                                 );
521                 }
522                 else {
523                         Log_Debug("FS_NTFS", " > StartingVCN = 0x%llx", attr->NonResident.StartingVCN);
524                         Log_Debug("FS_NTFS", " > LastVCN = 0x%llx", attr->NonResident.LastVCN);
525                         Log_Debug("FS_NTFS", " > DataRunOfs = 0x%x", attr->NonResident.DataRunOfs);
526                         Log_Debug("FS_NTFS", " > CompressionUnitSize = 0x%x", attr->NonResident.CompressionUnitSize);
527                         Log_Debug("FS_NTFS", " > AllocatedSize = 0x%llx", attr->NonResident.AllocatedSize);
528                         Log_Debug("FS_NTFS", " > RealSize = 0x%llx", attr->NonResident.RealSize);
529                         Log_Debug("FS_NTFS", " > InitiatedSize = 0x%llx", attr->NonResident.InitiatedSize);
530                         Debug_HexDump("FS_NTFS",
531                                 (char*)attr + attr->NonResident.DataRunOfs,
532                                 attr->Size - attr->NonResident.DataRunOfs
533                                 );
534                 }
535                 
536                 attr = (void*)( (tVAddr)attr + attr->Size );
537         }
538         
539         free(hdr);
540 }

UCC git Repository :: git.ucc.asn.au