NTFS文件系统解析(二)

1、引言

NTFS文件系统的的工作流程主要依赖于两个部分,文件记录部分用于记录文件本身的信息,而索引部分则通过树的形式存储着文件系统的结构信息。
而无论是文件记录还是索引,NTFS文件系统都为其设计了标准的数据结构,便于解析。

2、结构

从结构层面上说,无论文件记录还是索引,其本身的结构都相对简单,如下所示:
| 文件记录头 | 属性列表 | 属性列表 |… …| 结束标志 |
| 索引记录头 | 索引头 | 索引数据 | … … | 结束标志 |

// File Record Layout
// Record Header
// Attribute
// Attribute
// ...
// End Marker (0xFFFFFFFF)
typedef struct {
  b32 magic;          // MFT标志,始终为"FILE"
  ub16 usn_offset;    // 更新序列号USN(update sequence number)偏移
  ub16 usn_size;      // 更新序列号数组大小,单位:word
  b64 lsn;            // 日志文件序列号($LogFile Sequence Number,LSN)
  ub16 sn;            // 序列号(used times of this record)
  ub16 hard_links;    // 硬链接数
  ub16 attr_offset;   // 第一个属性列表的偏移
  ub16 flags;         // 标志(Flag),00H表示文件被删除,01H表示文件正在使用,02H表示目录被删除,03H表示目录正在使用
  b32 used;           // 文件记录实际使用的长度,可以理解为文件大小
  b32 alloced;        // 文件记录分配的长度,可以理解为磁盘占用大小
  b64 record_ref;     // 基本文件记录的文件索引号。当一个文件存在0x20属性列表时,用于区分是否主文件记录。
  ub16 next_attr;     // 下一属性ID
  ub16 border;        // 填充
  b32 record_number;  // 文件记录的参考号
} FileRecordHeader;

typedef struct {
  // Index Record Header
  b32 magic;        // 索引记录标志,始终为"INDX"
  ub16 usn_offset;  // 更新序列号USN(update sequence number)偏移
  ub16 usn_size;    // 更新序列号数组大小,单位:word
  b64 lsn;          // 日志文件序列号
  b64 vcn;          // 虚拟簇号
  // Index Header
  b32 entry_offset;  // 第一个索引的偏移
  b32 total_size;    // 所有索引的实际大小
  b32 alloc_size;    // 所有索引的分配大小
  b8 flags;          // 标志(Flag),1表示存在子节点,2表示叶子节点
  b8 padding[3];     // 填充
} IndexBlock;

3、更新序列号(USN)

NTFS文件系统为了保持数据的一致性,在文件和索引记录的每个扇区的最后2个字节都会写入更新序列号。正常情况下,不需要考虑这种特殊情况。
但是,当一个记录的恰巧写到扇区最后2个字节时,由于扇区尾部已经写入了2个字节的更新序列号。因此,NTFS文件系统会将本应写入扇区尾部的2个字节记录到文件记录的USN数组中。所以当需要解析文件记录时,我们需要将USN数组中的内容还原到对应的位置上去。

bool CFileRecord::PatchUS(b16 *sector_pointer, b32 sector_num, b16 usn,
                          b16 *us_data) const {
  // 遍历文件激烈的所有扇区
  for (b32 i = 0; i < sector_num; i++) {
    // 偏移到扇区的最后一个字
    sector_pointer += ((ntfs_volume_->sector_size_ >> 1) - 1);
    // 检测更新序列号是否一致
    if (*sector_pointer != usn) return false;  // USN check
    // 还原数据
    *sector_pointer = us_data[i];              // set data to update us
    sector_pointer++;
  }
  return true;
}

// 当读取到文件记录时
if (frh->magic == kFileRecordMagic) {
  // 计算出USN数组的位置
  b16 *usn_addr = (b16 *)((b8 *)frh + frh->usn_offset);
  // 第一个字存储USN
  b16 usn = *usn_addr;
  // 第二个字开始存储实际数据
  b16 *usarray = usn_addr + 1;
  if (PatchUS((b16 *)frh,
              ntfs_volume_->mft_size_ / ntfs_volume_->sector_size_, usn,
              usarray)) {
    file_record_ = frh;
    return true;
  }
}

你可能感兴趣的:(文件系统,NTFS)