3 * By John Hodge (thePowersGang)
8 * Reference: ntfsdoc.pdf
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);
31 MODULE_DEFINE(0, 0x0A /*v0.1*/, FS_NTFS, NTFS_Install, NULL);
32 tVFS_Driver gNTFS_FSInfo = {
34 .Detect = NTFS_Detect,
35 .InitDevice = NTFS_InitDevice,
36 .Unmount = NTFS_Unmount,
37 .GetNodeFromINode = NULL
39 tVFS_NodeType gNTFS_DirType = {
40 .TypeName = "NTFS-Dir",
41 .ReadDir = NTFS_ReadDir,
42 .FindDir = NTFS_FindDir,
45 tVFS_NodeType gNTFS_FileType = {
46 .TypeName = "NTFS-File",
47 .Read = NTFS_ReadFile,
51 tNTFS_Disk gNTFS_Disks;
55 * \brief Installs the NTFS driver
57 int NTFS_Install(char **Arguments)
59 VFS_AddDriver( &gNTFS_FSInfo );
64 * \brief Detect if a volume is NTFS
66 int NTFS_Detect(int FD)
69 VFS_ReadAt(FD, 0, 512, &bs);
71 if( bs.BytesPerSector == 0 || (bs.BytesPerSector & 511) )
73 if( bs.SectorsPerCluster == 0 )
75 if( bs.ClustersPerMFTRecord == 0 )
78 Uint64 ncluster = bs.TotalSectorCount / bs.SectorsPerCluster;
79 if( bs.MFTStart >= ncluster || bs.MFTMirrorStart >= ncluster )
82 if( memcmp(bs.SystemID, "NTFS ", 8) != 0 )
89 * \brief Mount a NTFS volume
91 tVFS_Node *NTFS_InitDevice(const char *Device, const char **Options)
96 disk = calloc( sizeof(tNTFS_Disk), 1 );
98 disk->FD = VFS_Open(Device, VFS_OPENFLAG_READ);
104 VFS_ReadAt(disk->FD, 0, 512, &bs);
107 Log_Debug("FS_NTFS", "Jump = %02x%02x%02x",
111 Log_Debug("FS_NTFS", "SystemID = %02x%02x%02x%02x%02x%02x%02x%02x (%8C)",
112 bs.SystemID[0], bs.SystemID[1], bs.SystemID[2], bs.SystemID[3],
113 bs.SystemID[4], bs.SystemID[5], bs.SystemID[6], bs.SystemID[7],
116 Log_Debug("FS_NTFS", "BytesPerSector = %i", bs.BytesPerSector);
117 Log_Debug("FS_NTFS", "SectorsPerCluster = %i", bs.SectorsPerCluster);
118 Log_Debug("FS_NTFS", "MediaDescriptor = 0x%x", bs.MediaDescriptor);
119 Log_Debug("FS_NTFS", "SectorsPerTrack = %i", bs.SectorsPerTrack);
120 Log_Debug("FS_NTFS", "Heads = %i", bs.Heads);
121 Log_Debug("FS_NTFS", "TotalSectorCount = 0x%llx", bs.TotalSectorCount);
122 Log_Debug("FS_NTFS", "MFTStart = 0x%llx", bs.MFTStart);
123 Log_Debug("FS_NTFS", "MFTMirrorStart = 0x%llx", bs.MFTMirrorStart);
124 Log_Debug("FS_NTFS", "ClustersPerMFTRecord = %i", bs.ClustersPerMFTRecord);
125 Log_Debug("FS_NTFS", "ClustersPerIndexRecord = %i", bs.ClustersPerIndexRecord);
126 Log_Debug("FS_NTFS", "SerialNumber = 0x%llx", bs.SerialNumber);
129 disk->ClusterSize = bs.BytesPerSector * bs.SectorsPerCluster;
130 ASSERTR(disk->ClusterSize > 0, NULL);
131 disk->MFTBase = bs.MFTStart;
132 Log_Debug("NTFS", "Cluster Size = %i KiB", disk->ClusterSize/1024);
133 Log_Debug("NTFS", "MFT Base = %i", disk->MFTBase);
134 Log_Debug("NTFS", "TotalSectorCount = 0x%x", bs.TotalSectorCount);
136 if( bs.ClustersPerMFTRecord < 0 ) {
137 disk->MFTRecSize = 1 << (-bs.ClustersPerMFTRecord);
140 disk->MFTRecSize = bs.ClustersPerMFTRecord * disk->ClusterSize;
142 ASSERTR(disk->MFTRecSize > 0, NULL);
143 //NTFS_DumpEntry(disk, 0); // $MFT
144 //NTFS_DumpEntry(disk, 3); // $VOLUME
146 disk->InodeCache = Inode_GetHandle(NTFS_FreeNode);
148 disk->MFTDataAttr = NULL;
149 disk->MFTDataAttr = NTFS_GetAttrib(disk, 0, NTFS_FileAttrib_Data, "", 0);
150 //NTFS_DumpEntry(disk, 5); // .
152 disk->RootDir.I30Root = NTFS_GetAttrib(disk, 5, NTFS_FileAttrib_IndexRoot, "$I30", 0);
153 disk->RootDir.I30Allocation = NTFS_GetAttrib(disk, 5, NTFS_FileAttrib_IndexAllocation, "$I30", 0);
154 disk->RootDir.Node.Inode = 5; // MFT Ent #5 is filesystem root
155 disk->RootDir.Node.ImplPtr = disk;
156 disk->RootDir.Node.Type = &gNTFS_DirType;
157 disk->RootDir.Node.Flags = VFS_FFLAG_DIRECTORY;
159 disk->RootDir.Node.UID = 0;
160 disk->RootDir.Node.GID = 0;
162 disk->RootDir.Node.NumACLs = 1;
163 disk->RootDir.Node.ACLs = &gVFS_ACL_EveryoneRX;
167 // Read from allocation
168 char buf[disk->ClusterSize];
169 size_t len = NTFS_ReadAttribData(disk->RootDir.I30Allocation, 0, sizeof(buf), buf);
170 Debug_HexDump("RootDir allocation", buf, len);
174 return &disk->RootDir.Node;
178 * \brief Unmount an NTFS Disk
180 void NTFS_Unmount(tVFS_Node *Node)
182 tNTFS_Disk *Disk = Node->ImplPtr;
187 void NTFS_Close(tVFS_Node *Node)
189 tNTFS_Disk *Disk = Node->ImplPtr;
190 Inode_UncacheNode(Disk->InodeCache, Node->Inode);
193 void NTFS_FreeNode(tVFS_Node *Node)
195 if( Node->Type == &gNTFS_DirType ) {
196 tNTFS_Directory *Dir = (void*)Node;
197 NTFS_FreeAttrib(Dir->I30Root);
198 NTFS_FreeAttrib(Dir->I30Allocation);
201 tNTFS_File *File = (void*)Node;
202 NTFS_FreeAttrib(File->Data);
206 int NTFS_int_ApplyUpdateSequence(void *Buffer, size_t BufLen, const Uint16 *Sequence, size_t NumEntries)
208 Uint16 cksum = Sequence[0];
209 LOG("cksum = %04x", cksum);
211 Uint16 *buf16 = Buffer;
212 for( int i = 0; i < NumEntries-1; i ++ )
214 size_t ofs = (i+1)*512 - 2;
215 if( ofs + 2 > BufLen ) {
217 Log_Warning("NTFS", "%x > %x", ofs+2, BufLen);
219 Uint16 *cksum_word = &buf16[ofs/2];
220 LOG("[%i]: %04x => %04x", i, Sequence[i], *cksum_word);
221 if( *cksum_word != cksum ) {
222 Log_Warning("NTFS", "Disk corruption detected");
225 *cksum_word = Sequence[i];
230 tNTFS_FILE_Header *NTFS_GetMFT(tNTFS_Disk *Disk, Uint32 MFTEntry)
232 // TODO: Cache MFT allocation for short-term
235 tNTFS_FILE_Header *ret = malloc( Disk->MFTRecSize );
237 Log_Warning("FS_NTFS", "malloc() fail!");
241 // NOTE: The MFT is a file, and can get fragmented
242 if( !Disk->MFTDataAttr ) {
243 VFS_ReadAt( Disk->FD,
244 Disk->MFTBase * Disk->ClusterSize + MFTEntry * Disk->MFTRecSize,
249 NTFS_ReadAttribData(Disk->MFTDataAttr, MFTEntry * Disk->MFTRecSize, Disk->MFTRecSize, ret);
252 NTFS_int_ApplyUpdateSequence(ret, Disk->MFTRecSize,
253 (void*)((char*)ret + ret->UpdateSequenceOfs), ret->UpdateSequenceSize
259 void NTFS_ReleaseMFT(tNTFS_Disk *Disk, Uint32 MFTEntry, tNTFS_FILE_Header *Entry)
264 static inline Uint64 _getVariableLengthInt(const void *Ptr, int Length, int bExtend)
266 const Uint8 *data = Ptr;
268 for( int i = 0; i < Length; i ++ )
269 bits |= (Uint64)data[i] << (i*8);
270 if( bExtend && Length && data[Length-1] & 0x80 ) {
271 for( int i = Length; i < 8; i ++ )
272 bits |= 0xFF << (i*8);
277 const void *_GetDataRun(const void *ptr, const void *limit, Uint64 LastLCN, Uint64 *Count, Uint64 *LCN)
281 LOG("Clean end of list");
285 const Uint8 *data = ptr;
288 Uint8 ofsSize = data[0] >> 4;
289 Uint8 lenSize = data[0] & 0xF;
290 LOG("ofsSize = %i, lenSize = %i", ofsSize, lenSize);
293 if( lenSize > 8 || lenSize < 1 )
295 if( data + 1 + ofsSize + lenSize > (const Uint8*)limit )
299 *Count = _getVariableLengthInt(data + 1, lenSize, 0);
302 *LCN = LastLCN + (Sint64)_getVariableLengthInt(data + 1 + lenSize, ofsSize, 1);
305 return data + 1 + ofsSize + lenSize;
308 tNTFS_Attrib *NTFS_GetAttrib(tNTFS_Disk *Disk, Uint32 MFTEntry, int Type, const char *Name, int DesIdx)
310 ENTER("pDisk xMFTEntry xType sName iDesIdx",
311 Disk, MFTEntry, Type, Name, DesIdx);
313 // TODO: Scan cache of attributes
316 tNTFS_FILE_Header *hdr = NTFS_GetMFT(Disk, MFTEntry);
317 LOG("hdr = %p", hdr);
319 tNTFS_FILE_Attrib *attr;
320 for( size_t ofs = hdr->FirstAttribOfs; ofs < hdr->RecordSize; ofs += attr->Size )
322 attr = (void*)( (tVAddr)hdr + ofs );
324 if( ofs + 4 > hdr->RecordSize )
327 if( attr->Type == 0xFFFFFFFF )
329 // Sanity #2: Type,Size
330 if( ofs + 8 > hdr->RecordSize )
332 // Sanity #3: Reported size
333 if( attr->Size < sizeof(attr->Resident) )
335 // Sanity #4: Reported size fits
336 if( ofs + attr->Size > hdr->RecordSize )
339 // - Chceck if this attribute is the one requested
340 LOG("Type check %x == %x", attr->Type, Type);
341 if( attr->Type != Type )
344 if( attr->NameOffset + attr->NameLength*2 > attr->Size ) {
347 const void *name16 = (char*)attr + attr->NameOffset;
348 LOG("Name check: '%s' == '%.*ls'",
349 Name, attr->NameLength, name16);
350 if( UTF16_CompareWithUTF8(attr->NameLength, name16, Name) != 0 )
353 LOG("Idx check %i", curIdx);
354 if( curIdx++ != DesIdx )
357 // - Construct (and cache) attribute description
358 ASSERT(attr->NameOffset % 1 == 0);
359 Uint16 *name16 = (Uint16*)attr + attr->NameOffset/2;
360 size_t namelen = UTF16_ConvertToUTF8(0, NULL, attr->NameLength, name16);
361 size_t edatalen = (attr->NonresidentFlag ? 0 : attr->Resident.AttribLen);
362 tNTFS_Attrib *ret = malloc( sizeof(tNTFS_Attrib) + namelen + 1 + edatalen );
366 if( attr->NonresidentFlag )
367 ret->Name = (void*)(ret + 1);
369 ret->ResidentData = ret + 1;
370 ret->Name = (char*)ret->ResidentData + edatalen;
374 ret->Type = attr->Type;
375 UTF16_ConvertToUTF8(namelen+1, ret->Name, attr->NameLength, name16);
376 ret->IsResident = !(attr->NonresidentFlag);
378 LOG("Creating with %x '%s'", ret->Type, ret->Name);
380 if( attr->NonresidentFlag )
382 ret->DataSize = attr->NonResident.RealSize;
383 ret->NonResident.CompressionUnitL2Size = attr->NonResident.CompressionUnitSize;
384 ret->NonResident.FirstPopulatedCluster = attr->NonResident.StartingVCN;
386 const char *limit = (char*)attr + attr->Size;
388 const char *datarun = (char*)attr + attr->NonResident.DataRunOfs;
389 while( (datarun = _GetDataRun(datarun, limit, 0, NULL, NULL)) )
391 LOG("nruns = %i", nruns);
392 // Allocate data runs
393 ret->NonResident.nRuns = nruns;
394 ret->NonResident.Runs = malloc( sizeof(tNTFS_AttribDataRun) * nruns );
396 datarun = (char*)attr + attr->NonResident.DataRunOfs;
398 while( datarun && i < nruns )
400 tNTFS_AttribDataRun *run = &ret->NonResident.Runs[i];
401 datarun = _GetDataRun(datarun,limit, lastLCN, &run->Count, &run->LCN);
402 LOG("Run %i: %llx+%llx", i, run->LCN, run->Count);
409 ret->DataSize = edatalen;
410 memcpy(ret->ResidentData, (char*)attr + attr->Resident.AttribOfs, edatalen);
411 Debug_HexDump("GetAttrib Resident", ret->ResidentData, edatalen);
414 NTFS_ReleaseMFT(Disk, MFTEntry, hdr);
420 NTFS_ReleaseMFT(Disk, MFTEntry, hdr);
425 void NTFS_FreeAttrib(tNTFS_Attrib *Attrib)
431 size_t NTFS_ReadAttribData(tNTFS_Attrib *Attrib, Uint64 Offset, size_t Length, void *Buffer)
435 if( Offset >= Attrib->DataSize )
437 if( Length > Attrib->DataSize )
438 Length = Attrib->DataSize;
439 if( Offset + Length > Attrib->DataSize )
440 Length = Attrib->DataSize - Offset;
442 if( Attrib->IsResident )
444 memcpy(Buffer, Attrib->ResidentData, Length);
450 tNTFS_Disk *Disk = Attrib->Disk;
451 Uint64 first_cluster = Offset / Disk->ClusterSize;
452 size_t cluster_ofs = Offset % Disk->ClusterSize;
453 if( first_cluster < Attrib->NonResident.FirstPopulatedCluster ) {
454 Log_Warning("NTFS", "TODO: Ofs < FirstVCN");
456 first_cluster -= Attrib->NonResident.FirstPopulatedCluster;
457 if( Attrib->NonResident.CompressionUnitL2Size )
460 Log_Warning("NTFS", "Compression unsupported");
461 // NOTE: Compressed blocks show up in pairs of runs
462 // - The first contains the compressed data
463 // - The second is a placeholder 'sparse' (LCN=0) to align to the compression unit
467 // Iterate through data runs until the desired run is located
468 for( int i = 0; i < Attrib->NonResident.nRuns && Length; i ++ )
470 tNTFS_AttribDataRun *run = &Attrib->NonResident.Runs[i];
471 if( first_cluster > run->Count ) {
472 first_cluster -= run->Count;
475 size_t avail_bytes = (run->Count-first_cluster)*Disk->ClusterSize - cluster_ofs;
476 if( avail_bytes > Length )
477 avail_bytes = Length;
478 // Read from this extent
479 if( run->LCN == 0 ) {
480 memset(Buffer, 0, avail_bytes);
484 (run->LCN + first_cluster)*Disk->ClusterSize + cluster_ofs,
489 Length -= avail_bytes;
490 Buffer += avail_bytes;
502 * \brief Dumps a MFT Entry
504 void NTFS_DumpEntry(tNTFS_Disk *Disk, Uint32 Entry)
506 tNTFS_FILE_Attrib *attr;
510 tNTFS_FILE_Header *hdr = NTFS_GetMFT(Disk, Entry);
512 Log_Warning("FS_NTFS", "malloc() fail!");
516 Log_Debug("FS_NTFS", "MFT Entry #%i", Entry);
517 Log_Debug("FS_NTFS", "- Magic = 0x%08x (%4C)", hdr->Magic, &hdr->Magic);
518 Log_Debug("FS_NTFS", "- UpdateSequenceOfs = 0x%x", hdr->UpdateSequenceOfs);
519 Log_Debug("FS_NTFS", "- UpdateSequenceSize = 0x%x", hdr->UpdateSequenceSize);
520 Log_Debug("FS_NTFS", "- LSN = 0x%x", hdr->LSN);
521 Log_Debug("FS_NTFS", "- SequenceNumber = %i", hdr->SequenceNumber);
522 Log_Debug("FS_NTFS", "- HardLinkCount = %i", hdr->HardLinkCount);
523 Log_Debug("FS_NTFS", "- FirstAttribOfs = 0x%x", hdr->FirstAttribOfs);
524 Log_Debug("FS_NTFS", "- Flags = 0x%x", hdr->Flags);
525 Log_Debug("FS_NTFS", "- RecordSize = 0x%x", hdr->RecordSize);
526 Log_Debug("FS_NTFS", "- RecordSpace = 0x%x", hdr->RecordSpace);
527 Log_Debug("FS_NTFS", "- Reference = 0x%llx", hdr->Reference);
528 Log_Debug("FS_NTFS", "- NextAttribID = 0x%04x", hdr->NextAttribID);
530 attr = (void*)( (char*)hdr + hdr->FirstAttribOfs );
532 while( (tVAddr)attr < (tVAddr)hdr + hdr->RecordSize )
534 if(attr->Type == 0xFFFFFFFF) break;
535 Log_Debug("FS_NTFS", "- Attribute %i", i ++);
536 Log_Debug("FS_NTFS", " > Type = 0x%x", attr->Type);
537 Log_Debug("FS_NTFS", " > Size = 0x%x", attr->Size);
538 Log_Debug("FS_NTFS", " > ResidentFlag = 0x%x", attr->NonresidentFlag);
539 Log_Debug("FS_NTFS", " > NameLength = %i", attr->NameLength);
540 Log_Debug("FS_NTFS", " > NameOffset = 0x%x", attr->NameOffset);
541 Log_Debug("FS_NTFS", " > Flags = 0x%x", attr->Flags);
542 Log_Debug("FS_NTFS", " > AttributeID = 0x%x", attr->AttributeID);
544 Uint16 *name16 = (void*)((char*)attr + attr->NameOffset);
545 size_t len = UTF16_ConvertToUTF8(0, NULL, attr->NameLength, name16);
547 UTF16_ConvertToUTF8(len+1, name, attr->NameLength, name16);
548 Log_Debug("FS_NTFS", " > Name = '%s'", name);
550 if( !attr->NonresidentFlag ) {
551 Log_Debug("FS_NTFS", " > AttribLen = 0x%x", attr->Resident.AttribLen);
552 Log_Debug("FS_NTFS", " > AttribOfs = 0x%x", attr->Resident.AttribOfs);
553 Log_Debug("FS_NTFS", " > IndexedFlag = 0x%x", attr->Resident.IndexedFlag);
554 Debug_HexDump("FS_NTFS",
555 (void*)( (tVAddr)attr + attr->Resident.AttribOfs ),
556 attr->Resident.AttribLen
560 Log_Debug("FS_NTFS", " > StartingVCN = 0x%llx", attr->NonResident.StartingVCN);
561 Log_Debug("FS_NTFS", " > LastVCN = 0x%llx", attr->NonResident.LastVCN);
562 Log_Debug("FS_NTFS", " > DataRunOfs = 0x%x", attr->NonResident.DataRunOfs);
563 Log_Debug("FS_NTFS", " > CompressionUnitSize = 0x%x", attr->NonResident.CompressionUnitSize);
564 Log_Debug("FS_NTFS", " > AllocatedSize = 0x%llx", attr->NonResident.AllocatedSize);
565 Log_Debug("FS_NTFS", " > RealSize = 0x%llx", attr->NonResident.RealSize);
566 Log_Debug("FS_NTFS", " > InitiatedSize = 0x%llx", attr->NonResident.InitiatedSize);
567 Debug_HexDump("FS_NTFS",
568 (char*)attr + attr->NonResident.DataRunOfs,
569 attr->Size - attr->NonResident.DataRunOfs
573 attr = (void*)( (tVAddr)attr + attr->Size );
576 NTFS_ReleaseMFT(Disk, Entry, hdr);