【问题标题】:Insert bytes into middle of a file (in windows filesystem) without reading entire file (using File Allocation Table)?将字节插入文件中间(在 Windows 文件系统中)而不读取整个文件(使用文件分配表)?
【发布时间】:2012-11-05 23:34:39
【问题描述】:

我需要一种在文件中间插入一些文件簇以插入一些数据的方法。

通常情况下,我会读取整个文件,然后将其与更改一起重新写回,但文件大小为数 GB,读取文件并再次将其写回需要 30 分钟。

集群大小并不困扰我;我基本上可以在我插入的簇的末尾写出零,它仍然可以在这种文件格式下工作。

如何使用 Windows 文件 API(或其他机制)修改文件的文件分配表,在文件中间的指定点插入一个或多个未使用的集群?

【问题讨论】:

  • @MarkRansom:我可以使插入的字节恰好是集群大小的倍数。有问题的文件有很多千兆字节,所以我可以向你保证,这是值得的。目前执行这样的编辑需要 30 分钟,所以我很乐意尽我所能。
  • 您最好将文件格式设计为允许在末尾添加内容,但看起来好像已插入。如果可以的话!
  • @minitech:什么都没有。我已经安装了它们。 :)
  • @MahmoudAl-Qudsi:其他人似乎都认为它确实如此,所以如果你有一些好的想法,现在是时候在答案中提出它们了。
  • 你也需要压缩和稀疏文件的支持吗?这要复杂得多。

标签: c# windows file winapi filesystems


【解决方案1】:

抽象问题,抽象答案:

当然可以在 FAT 中执行此操作,并且可能在大多数其他 FS 中执行此操作,本质上是对文件进行分段,而不是更常见的碎片整理过程。

FAT 是用簇指针组织的,簇指针产生一个簇号链,其中存储数据,第一个链接索引与文件记录一起存储,第二个存储在分配表中的索引 [第一个链接的编号]等等。只要您插入的数据在集群的边界处结束,就可以在链中的任何位置插入另一个链接。

通过找到open source library,您可能会更加更轻松地在 C 中执行此操作。虽然在 C# 中使用 PInvoke 可能可以做到这一点,但您不会发现任何好的示例代码可供您开始使用。

我怀疑您对文件格式(视频文件?)没有任何控制权,如果您这样做,那么首先设计数据存储以避免该问题会容易得多。

【讨论】:

  • 我没有意识到这个问题是抽象的。我真的不想以这种方式来选择别人的问题,但我现在已经重写它以非常准确地表达我的问题。
  • @RobertHarvey 你没有向我们展示你做了什么,你尝试了什么,你在尝试中使用了什么或者你遇到的任何特定问题,所以是的,它是抽象的。
  • 这不是故障排除问题。我不是在问修复损坏的代码。
  • @RobertHarvey:完全正确!你问能不能做到:是的,能做到。
  • 如果你知道怎么做,现在是发布详细示例的时候了。
【解决方案2】:

罗伯特,我不认为如果不主动操作文件系统的文件系统数据结构,你想要实现的目标实际上是挂载的。我想我不必告诉你如何危险不明智这种锻炼方式吧。

但如果你需要这样做,我想我可以给你一张“餐巾纸背面的草图”让你开始:

您可以利用 NTFS 的“稀疏文件”支持通过调整 LCN/VCN 映射来简单地添加“间隙”。完成后,只需打开文件,寻找新位置并写入数据。 NTFS 将透明地分配空间并将数据写入文件中间,您在这里创建了一个洞。

有关更多信息,请查看有关 NTFS 中defragmentation support 的页面,以获取有关如何稍微操作事物并允许您在文件中间插入簇的提示。至少通过对此类事情使用认可的 API,您不太可能将文件系统损坏到无法修复的程度,尽管我猜您仍然可以可怕地处理您的文件。

获取所需文件的检索指针,将它们拆分到需要的位置,根据需要添加尽可能多的额外空间,然后移动文件。 Russinovich/Ionescu “Windows Internals”一书中有一个关于这类事情的有趣章节 (http://www.amazon.com/Windows%C2%AE-Internals-Including-Windows-Developer/dp/0735625301)

【讨论】:

  • 到目前为止,这几乎是我已经弄清楚的。如果没有一些已经这样做的软件库,我不希望任何人能够立即回答这个问题,除非他们以前这样做过。赏金到期前有 7 天的窗口期。我愿意等待。 :)
  • 我想我可以玩一下这个......反正谁需要睡觉? ;)
  • 顺便说一句,从 Windows8 的 NTFS 中删除了稀疏文件支持,并且在 FAT32 中也不存在。此外,即使在稀疏文件中,您也不能随机插入部分。如果写入了字节 0-100 和 100-200,即使在稀疏文件中,也无法在位置 100 处插入 100 个字节。
  • 我的论点是使用碎片整理 API 重新排列文件的 LCN/VCN 映射并有效地创建一个“洞”,该“洞”将被解释为文件的稀疏部分。
  • @ixe013: FSCTL_MOVE_FILE 大致相当于那个。诀窍是它不会让你调整 VCN。
【解决方案3】:

[编辑:]

废话 - 我要说“这不可行,至少不能通过 MFT 修改,没有很多痛苦”;首先,NTFS MFT 结构本身并不是 100%“开放”的,所以我开始深入研究逆向工程领域,它具有我没有心情处理的法律影响。此外,在 .NET 中执行此操作是一个非常繁琐的映射和编组结构的过程,基于大量的猜测(并且不要让我开始了解大多数 MFT 结构都以奇怪的方式压缩的事实)。短篇小说,虽然我确实学到了很多关于 NTFS 如何“工作”的知识,但我离这个问题的解决方案还差得远。

[/编辑]

呃……废话太多了……

这让我觉得“很有趣”,因此我不得不在这个问题上四处寻找......它仍然是一个“正在进行中的答案”,但想发布我所需要帮助其他人提出的所有问题有东西。 :)

另外,我有一个粗略的感觉,这在 FAT32 上会容易得多,但考虑到我只有 NTFS 可以使用......

所以 - 很多调用和编组,所以让我们从那里开始并向后工作:

正如人们可能猜到的那样,标准 .NET 文件/IO api 在这里对您没有多大帮助 - 我们需要 设备级 访问:

[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern SafeFileHandle CreateFile(
    string lpFileName,
    [MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
    [MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
    IntPtr lpSecurityAttributes,
    [MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
    [MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
    IntPtr hTemplateFile);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool ReadFile(
    SafeFileHandle hFile,      // handle to file
    byte[] pBuffer,        // data buffer, should be fixed
    int NumberOfBytesToRead,  // number of bytes to read
    IntPtr pNumberOfBytesRead,  // number of bytes read, provide NULL here
    ref NativeOverlapped lpOverlapped // should be fixed, if not null
);

[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool SetFilePointerEx(
    SafeFileHandle hFile,
    long liDistanceToMove,
    out long lpNewFilePointer,
    SeekOrigin dwMoveMethod);

我们将因此使用这些讨厌的 win32 野兽:

// To the metal, baby!
using (var fileHandle = NativeMethods.CreateFile(
    // Magic "give me the device" syntax
    @"\\.\c:",
    // MUST explicitly provide both of these, not ReadWrite
    FileAccess.Read | FileAccess.Write,
    // MUST explicitly provide both of these, not ReadWrite
    FileShare.Write | FileShare.Read,
    IntPtr.Zero,
    FileMode.Open,
    FileAttributes.Normal,
    IntPtr.Zero))
{
    if (fileHandle.IsInvalid)
    {
        // Doh!
        throw new Win32Exception();
    }
    else
    {
        // Boot sector ~ 512 bytes long
        byte[] buffer = new byte[512];
        NativeOverlapped overlapped = new NativeOverlapped();
        NativeMethods.ReadFile(fileHandle, buffer, buffer.Length, IntPtr.Zero, ref overlapped);

        // Pin it so we can transmogrify it into a FAT structure
        var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            // note, I've got an NTFS drive, change yours to suit
            var bootSector = (BootSector_NTFS)Marshal.PtrToStructure(
                 handle.AddrOfPinnedObject(), 
                 typeof(BootSector_NTFS));

哇,哇哇 - BootSector_NTFS 到底是什么?它是一个字节映射的struct,与我认为的 NTFS 结构(包括 FAT32)非常接近:

[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi, Pack=0)]
public struct JumpBoot
{
    [MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=3)]
    public byte[] BS_jmpBoot;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=8)]
    public string BS_OEMName;
}

[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Pack = 0, Size = 90)]
public struct BootSector_NTFS
{
    [FieldOffset(0)]
    public JumpBoot JumpBoot;
    [FieldOffset(0xb)]
    public short BytesPerSector;
    [FieldOffset(0xd)]
    public byte SectorsPerCluster;
    [FieldOffset(0xe)]
    public short ReservedSectorCount;
    [FieldOffset(0x10)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public byte[] Reserved0_MUSTBEZEROs;
    [FieldOffset(0x15)]
    public byte BPB_Media;
    [FieldOffset(0x16)]
    public short Reserved1_MUSTBEZERO;
    [FieldOffset(0x18)]
    public short SectorsPerTrack;
    [FieldOffset(0x1A)]
    public short HeadCount;
    [FieldOffset(0x1c)]
    public int HiddenSectorCount;
    [FieldOffset(0x20)]
    public int LargeSectors;
    [FieldOffset(0x24)]
    public int Reserved6;
    [FieldOffset(0x28)]
    public long TotalSectors;
    [FieldOffset(0x30)]
    public long MftClusterNumber;
    [FieldOffset(0x38)]
    public long MftMirrorClusterNumber;
    [FieldOffset(0x40)]
    public byte ClustersPerMftRecord;
    [FieldOffset(0x41)]
    public byte Reserved7;
    [FieldOffset(0x42)]
    public short Reserved8;
    [FieldOffset(0x44)]
    public byte ClustersPerIndexBuffer;
    [FieldOffset(0x45)]
    public byte Reserved9;
    [FieldOffset(0x46)]
    public short ReservedA;
    [FieldOffset(0x48)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
    public byte[] SerialNumber;
    [FieldOffset(0x50)]
    public int Checksum;
    [FieldOffset(0x54)]
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x1AA)]
    public byte[] BootupCode;
    [FieldOffset(0x1FE)]
    public ushort EndOfSectorMarker;

    public long GetMftAbsoluteIndex(int recordIndex = 0)
    {
        return (BytesPerSector * SectorsPerCluster * MftClusterNumber) + (GetMftEntrySize() * recordIndex);
    }
    public long GetMftEntrySize()
    {
        return (BytesPerSector * SectorsPerCluster * ClustersPerMftRecord);
    }
}


// Note: dont have fat32, so can't verify all these...they *should* work, tho
// refs:
//    http://www.pjrc.com/tech/8051/ide/fat32.html
//    http://msdn.microsoft.com/en-US/windows/hardware/gg463084
[StructLayout(LayoutKind.Explicit, CharSet=CharSet.Auto, Pack=0, Size=90)]
public struct BootSector_FAT32
{
    [FieldOffset(0)]
    public JumpBoot JumpBoot;    
    [FieldOffset(11)]
    public short BPB_BytsPerSec;
    [FieldOffset(13)]
    public byte BPB_SecPerClus;
    [FieldOffset(14)]
    public short BPB_RsvdSecCnt;
    [FieldOffset(16)]
    public byte BPB_NumFATs;
    [FieldOffset(17)]
    public short BPB_RootEntCnt;
    [FieldOffset(19)]
    public short BPB_TotSec16;
    [FieldOffset(21)]
    public byte BPB_Media;
    [FieldOffset(22)]
    public short BPB_FATSz16;
    [FieldOffset(24)]
    public short BPB_SecPerTrk;
    [FieldOffset(26)]
    public short BPB_NumHeads;
    [FieldOffset(28)]
    public int BPB_HiddSec;
    [FieldOffset(32)]
    public int BPB_TotSec32;
    [FieldOffset(36)]
    public FAT32 FAT;
}

[StructLayout(LayoutKind.Sequential)]
public struct FAT32
{
    public int BPB_FATSz32;
    public short BPB_ExtFlags;
    public short BPB_FSVer;
    public int BPB_RootClus;
    public short BPB_FSInfo;
    public short BPB_BkBootSec;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst=12)]
    public byte[] BPB_Reserved;
    public byte BS_DrvNum;
    public byte BS_Reserved1;
    public byte BS_BootSig;
    public int BS_VolID;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=11)] 
    public string BS_VolLab;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst=8)] 
    public string BS_FilSysType;
}

所以现在我们可以将整个混乱'o'字节映射回这个结构:

// Pin it so we can transmogrify it into a FAT structure
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
    try
    {            
        // note, I've got an NTFS drive, change yours to suit
        var bootSector = (BootSector_NTFS)Marshal.PtrToStructure(
              handle.AddrOfPinnedObject(), 
              typeof(BootSector_NTFS));
        Console.WriteLine(
            "I think that the Master File Table is at absolute position:{0}, sector:{1}", 
            bootSector.GetMftAbsoluteIndex(),
            bootSector.GetMftAbsoluteIndex() / bootSector.BytesPerSector);

此时输出:

I think that the Master File Table is at 
absolute position:3221225472, sector:6291456

让我们使用 OEM 支持工具nfi.exe 快速确认一下:

C:\tools\OEMTools\nfi>nfi c:
NTFS File Sector Information Utility.
Copyright (C) Microsoft Corporation 1999. All rights reserved.


File 0
Master File Table ($Mft)
    $STANDARD_INFORMATION (resident)
    $FILE_NAME (resident)
    $DATA (nonresident)
        logical sectors 6291456-6487039 (0x600000-0x62fbff)
        logical sectors 366267960-369153591 (0x15d4ce38-0x1600d637)
    $BITMAP (nonresident)
        logical sectors 6291448-6291455 (0x5ffff8-0x5fffff)
        logical sectors 7273984-7274367 (0x6efe00-0x6eff7f)

酷,看起来我们在正确的轨道上......继续前进!

            // If you've got LinqPad, uncomment this to look at boot sector
            bootSector.Dump();

    Console.WriteLine("Jumping to Master File Table...");
    long lpNewFilePointer;
    if (!NativeMethods.SetFilePointerEx(
            fileHandle, 
            bootSector.GetMftAbsoluteIndex(), 
            out lpNewFilePointer, 
            SeekOrigin.Begin))
    {
        throw new Win32Exception();
    }
    Console.WriteLine("Position now: {0}", lpNewFilePointer);

    // Read in one MFT entry
    byte[] mft_buffer = new byte[bootSector.GetMftEntrySize()];
    Console.WriteLine("Reading $MFT entry...calculated size: 0x{0}",
       bootSector.GetMftEntrySize().ToString("X"));

    var seekIndex = bootSector.GetMftAbsoluteIndex();
    overlapped.OffsetHigh = (int)(seekIndex >> 32);
    overlapped.OffsetLow = (int)seekIndex;
    NativeMethods.ReadFile(
          fileHandle, 
          mft_buffer, 
          mft_buffer.Length, 
          IntPtr.Zero, 
          ref overlapped);
    // Pin it for transmogrification
    var mft_handle = GCHandle.Alloc(mft_buffer, GCHandleType.Pinned);
    try
    {
        var mftRecords = (MFTSystemRecords)Marshal.PtrToStructure(
              mft_handle.AddrOfPinnedObject(), 
              typeof(MFTSystemRecords));
        mftRecords.Dump();
    }
    finally
    {
        // make sure we clean up
        mft_handle.Free();
    }
}
finally
{
    // make sure we clean up
    handle.Free();
}

啊,更多的原生结构要讨论 - 所以 MFT 被安排成前 16 个左右的条目是“固定的”:

[StructLayout(LayoutKind.Sequential)]
public struct MFTSystemRecords
{
    public MFTRecord Mft;
    public MFTRecord MftMirror;
    public MFTRecord LogFile;
    public MFTRecord Volume;
    public MFTRecord AttributeDefs;
    public MFTRecord RootFile;
    public MFTRecord ClusterBitmap;
    public MFTRecord BootSector;
    public MFTRecord BadClusterFile;
    public MFTRecord SecurityFile;
    public MFTRecord UpcaseTable;
    public MFTRecord ExtensionFile;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)]
    public MFTRecord[] MftReserved;
    public MFTRecord MftFileExt;
}

MFTRecord 在哪里:

[StructLayout(LayoutKind.Sequential, Size = 1024)]
public struct MFTRecord
{
    const int BASE_RECORD_SIZE = 48;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string Type;
    public short UsaOffset;
    public short UsaCount;
    public long Lsn;  /* $LogFile sequence number for this record. Changed every time the record is modified. */
    public short SequenceNumber; /* # of times this record has been reused */
    public short LinkCount;  /* Number of hard links, i.e. the number of directory entries referencing this record. */
    public short AttributeOffset; /* Byte offset to the first attribute in this mft record from the start of the mft record. */
    public short MftRecordFlags;
    public int BytesInUse;
    public int BytesAllocated;
    public long BaseFileRecord;
    public short NextAttributeNumber;
    public short Reserved;
    public int MftRecordNumber;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 976)]
    public byte[] Data;
    public byte[] SetData
    {
        get
        {
            return this.Data
               .Skip(AttributeOffset - BASE_RECORD_SIZE)
               .Take(BytesInUse - BASE_RECORD_SIZE)
               .ToArray();
        }
    }
    public MftAttribute[] Attributes
    {
        get
        {
            var idx = 0;
            var ret = new List<MftAttribute>();
            while (idx < SetData.Length)
            {
                var attr = MftAttribute.FromBytes(SetData.Skip(idx).ToArray());
                ret.Add(attr);
                idx += attr.Attribute.Length;
                // A special "END" attribute denotes the end of the list
                if (attr.Attribute.AttributeType == MftAttributeType.AT_END) break;
            }
            return ret.ToArray();
        }
    }
}

而且...这就是我现在逐渐消失的地方;主要是因为我想吃晚饭之类的。不过,我会回到这个话题!

参考资料(部分是为了我自己的记忆,部分是为了帮助其他调查人员)

完整代码转储如下:

我在上面看到的所有原生映射(由于帖子大小限制,不是完整的重新散列):

public enum MftRecordFlags : ushort
{
    MFT_RECORD_IN_USE = 0x0001,
    MFT_RECORD_IS_DIRECTORY = 0x0002,
    MFT_RECORD_IN_EXTEND = 0x0004,
    MFT_RECORD_IS_VIEW_INDEX = 0x0008,
    MFT_REC_SPACE_FILLER = 0xffff
}
public enum MftAttributeType : uint
{
    AT_UNUSED = 0,
    AT_STANDARD_INFORMATION = 0x10,
    AT_ATTRIBUTE_LIST = 0x20,
    AT_FILENAME = 0x30,
    AT_OBJECT_ID = 0x40,
    AT_SECURITY_DESCRIPTOR = 0x50,
    AT_VOLUME_NAME = 0x60,
    AT_VOLUME_INFORMATION = 0x70,
    AT_DATA = 0x80,
    AT_INDEX_ROOT = 0x90,
    AT_INDEX_ALLOCATION = 0xa0,
    AT_BITMAP = 0xb0,
    AT_REPARSE_POINT = 0xc0,
    AT_EA_INFORMATION = 0xd0,
    AT_EA = 0xe0,
    AT_PROPERTY_SET = 0xf0,
    AT_LOGGED_UTILITY_STREAM = 0x100,
    AT_FIRST_USER_DEFINED_ATTRIBUTE = 0x1000,
    AT_END = 0xffffffff
}

public enum MftAttributeDefFlags : byte
{
    ATTR_DEF_INDEXABLE = 0x02, /* Attribute can be indexed. */
    ATTR_DEF_MULTIPLE = 0x04, /* Attribute type can be present multiple times in the mft records of an inode. */
    ATTR_DEF_NOT_ZERO = 0x08, /* Attribute value must contain at least one non-zero byte. */
    ATTR_DEF_INDEXED_UNIQUE = 0x10, /* Attribute must be indexed and the attribute value must be unique for the attribute type in all of the mft records of an inode. */
    ATTR_DEF_NAMED_UNIQUE = 0x20, /* Attribute must be named and the name must be unique for the attribute type in all of the mft records of an inode. */
    ATTR_DEF_RESIDENT = 0x40, /* Attribute must be resident. */
    ATTR_DEF_ALWAYS_LOG = 0x80, /* Always log modifications to this attribute, regardless of whether it is resident or
                non-resident.  Without this, only log modifications if the attribute is resident. */
}

[StructLayout(LayoutKind.Explicit)]
public struct MftInternalAttribute
{
    [FieldOffset(0)]
    public MftAttributeType AttributeType;
    [FieldOffset(4)]
    public int Length;
    [FieldOffset(8)]
    [MarshalAs(UnmanagedType.Bool)]
    public bool NonResident;
    [FieldOffset(9)]
    public byte NameLength;
    [FieldOffset(10)]
    public short NameOffset;
    [FieldOffset(12)]
    public int AttributeFlags;
    [FieldOffset(14)]
    public short Instance;
    [FieldOffset(16)]
    public ResidentAttribute ResidentAttribute;
    [FieldOffset(16)]
    public NonResidentAttribute NonResidentAttribute;
}

[StructLayout(LayoutKind.Sequential)]
public struct ResidentAttribute
{
    public int ValueLength;
    public short ValueOffset;
    public byte ResidentAttributeFlags;
    public byte Reserved;

    public override string ToString()
    {
        return string.Format("{0}:{1}:{2}:{3}", ValueLength, ValueOffset, ResidentAttributeFlags, Reserved);
    }
}
[StructLayout(LayoutKind.Sequential)]
public struct NonResidentAttribute
{
    public long LowestVcn;
    public long HighestVcn;
    public short MappingPairsOffset;
    public byte CompressionUnit;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
    public byte[] Reserved;
    public long AllocatedSize;
    public long DataSize;
    public long InitializedSize;
    public long CompressedSize;
    public override string ToString()
    {
        return string.Format("{0}:{1}:{2}:{3}:{4}:{5}:{6}:{7}", LowestVcn, HighestVcn, MappingPairsOffset, CompressionUnit, AllocatedSize, DataSize, InitializedSize, CompressedSize);
    }
}

public struct MftAttribute
{
    public MftInternalAttribute Attribute;

    [field: NonSerialized]
    public string Name;

    [field: NonSerialized]
    public byte[] Data;

    [field: NonSerialized]
    public object Payload;

    public static MftAttribute FromBytes(byte[] buffer)
    {
        var hnd = GCHandle.Alloc(buffer, GCHandleType.Pinned);
        try
        {
            var attr = (MftInternalAttribute)Marshal.PtrToStructure(hnd.AddrOfPinnedObject(), typeof(MftInternalAttribute));
            var ret = new MftAttribute() { Attribute = attr };
            ret.Data = buffer.Skip(Marshal.SizeOf(attr)).Take(attr.Length).ToArray();
            if (ret.Attribute.AttributeType == MftAttributeType.AT_STANDARD_INFORMATION)
            {
                var payloadHnd = GCHandle.Alloc(ret.Data, GCHandleType.Pinned);
                try
                {
                    var payload = (MftStandardInformation)Marshal.PtrToStructure(payloadHnd.AddrOfPinnedObject(), typeof(MftStandardInformation));
                    ret.Payload = payload;
                }
                finally
                {
                    payloadHnd.Free();
                }
            }
            return ret;
        }
        finally
        {
            hnd.Free();
        }
    }
}

[StructLayout(LayoutKind.Sequential)]
public struct MftStandardInformation
{
    public ulong CreationTime;
    public ulong LastDataChangeTime;
    public ulong LastMftChangeTime;
    public ulong LastAccessTime;
    public int FileAttributes;
    public int MaximumVersions;
    public int VersionNumber;
    public int ClassId;
    public int OwnerId;
    public int SecurityId;
    public long QuotaChanged;
    public long Usn;
}

// Note: dont have fat32, so can't verify all these...they *should* work, tho
// refs:
//    http://www.pjrc.com/tech/8051/ide/fat32.html
//    http://msdn.microsoft.com/en-US/windows/hardware/gg463084
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Auto, Pack = 0, Size = 90)]
public struct BootSector_FAT32
{
    [FieldOffset(0)]
    public JumpBoot JumpBoot;
    [FieldOffset(11)]
    public short BPB_BytsPerSec;
    [FieldOffset(13)]
    public byte BPB_SecPerClus;
    [FieldOffset(14)]
    public short BPB_RsvdSecCnt;
    [FieldOffset(16)]
    public byte BPB_NumFATs;
    [FieldOffset(17)]
    public short BPB_RootEntCnt;
    [FieldOffset(19)]
    public short BPB_TotSec16;
    [FieldOffset(21)]
    public byte BPB_Media;
    [FieldOffset(22)]
    public short BPB_FATSz16;
    [FieldOffset(24)]
    public short BPB_SecPerTrk;
    [FieldOffset(26)]
    public short BPB_NumHeads;
    [FieldOffset(28)]
    public int BPB_HiddSec;
    [FieldOffset(32)]
    public int BPB_TotSec32;
    [FieldOffset(36)]
    public FAT32 FAT;
}

[StructLayout(LayoutKind.Sequential)]
public struct FAT32
{
    public int BPB_FATSz32;
    public short BPB_ExtFlags;
    public short BPB_FSVer;
    public int BPB_RootClus;
    public short BPB_FSInfo;
    public short BPB_BkBootSec;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 12)]
    public byte[] BPB_Reserved;
    public byte BS_DrvNum;
    public byte BS_Reserved1;
    public byte BS_BootSig;
    public int BS_VolID;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 11)]
    public string BS_VolLab;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 8)]
    public string BS_FilSysType;
}

以及测试工具:

class Program
{        
    static void Main(string[] args)
    {
        // To the metal, baby!
        using (var fileHandle = NativeMethods.CreateFile(
            // Magic "give me the device" syntax
            @"\\.\c:",
            // MUST explicitly provide both of these, not ReadWrite
            FileAccess.Read | FileAccess.Write,
            // MUST explicitly provide both of these, not ReadWrite
            FileShare.Write | FileShare.Read,
            IntPtr.Zero,
            FileMode.Open,
            FileAttributes.Normal,
            IntPtr.Zero))
        {
            if (fileHandle.IsInvalid)
            {
                // Doh!
                throw new Win32Exception();
            }
            else
            {
                // Boot sector ~ 512 bytes long
                byte[] buffer = new byte[512];
                NativeOverlapped overlapped = new NativeOverlapped();
                NativeMethods.ReadFile(fileHandle, buffer, buffer.Length, IntPtr.Zero, ref overlapped);

                // Pin it so we can transmogrify it into a FAT structure
                var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
                try
                {
                    // note, I've got an NTFS drive, change yours to suit
                    var bootSector = (BootSector_NTFS)Marshal.PtrToStructure(handle.AddrOfPinnedObject(), typeof(BootSector_NTFS));
                    Console.WriteLine(
                        "I think that the Master File Table is at absolute position:{0}, sector:{1}",
                        bootSector.GetMftAbsoluteIndex(),
                        bootSector.GetMftAbsoluteIndex() / bootSector.BytesPerSector);
                    Console.WriteLine("MFT record size:{0}", bootSector.ClustersPerMftRecord * bootSector.SectorsPerCluster * bootSector.BytesPerSector);

                    // If you've got LinqPad, uncomment this to look at boot sector
                    bootSector.DumpToHtmlString();

                    Pause();

                    Console.WriteLine("Jumping to Master File Table...");
                    long lpNewFilePointer;
                    if (!NativeMethods.SetFilePointerEx(fileHandle, bootSector.GetMftAbsoluteIndex(), out lpNewFilePointer, SeekOrigin.Begin))
                    {
                        throw new Win32Exception();
                    }
                    Console.WriteLine("Position now: {0}", lpNewFilePointer);

                    // Read in one MFT entry
                    byte[] mft_buffer = new byte[bootSector.GetMftEntrySize()];
                    Console.WriteLine("Reading $MFT entry...calculated size: 0x{0}", bootSector.GetMftEntrySize().ToString("X"));

                    var seekIndex = bootSector.GetMftAbsoluteIndex();
                    overlapped.OffsetHigh = (int)(seekIndex >> 32);
                    overlapped.OffsetLow = (int)seekIndex;
                    NativeMethods.ReadFile(fileHandle, mft_buffer, mft_buffer.Length, IntPtr.Zero, ref overlapped);
                    // Pin it for transmogrification
                    var mft_handle = GCHandle.Alloc(mft_buffer, GCHandleType.Pinned);
                    try
                    {
                        var mftRecords = (MFTSystemRecords)Marshal.PtrToStructure(mft_handle.AddrOfPinnedObject(), typeof(MFTSystemRecords));
                        mftRecords.DumpToHtmlString();
                    }
                    finally
                    {
                        // make sure we clean up
                        mft_handle.Free();
                    }
                }
                finally
                {
                    // make sure we clean up
                    handle.Free();
                }
            }
        }
        Pause();
    }

    private static void Pause()
    {
        Console.WriteLine("Press enter to continue...");
        Console.ReadLine();
    }
}


public static class Dumper
{
    public static string DumpToHtmlString<T>(this T objectToSerialize)
    {
        string strHTML = "";
        try
        {
            var writer = LINQPad.Util.CreateXhtmlWriter(true);
            writer.Write(objectToSerialize);
            strHTML = writer.ToString();
        }
        catch (Exception exc)
        {
            Debug.Assert(false, "Investigate why ?" + exc);
        }

        var shower = new Thread(
            () =>
                {
                    var dumpWin = new Window();
                    var browser = new WebBrowser();
                    dumpWin.Content = browser;
                    browser.NavigateToString(strHTML);
                    dumpWin.ShowDialog();                        
                });
        shower.SetApartmentState(ApartmentState.STA);
        shower.Start();
        return strHTML;
    }

    public static string Dump(this object value)
    {
         return JsonConvert.SerializeObject(value, Formatting.Indented);
    }
}

【讨论】:

  • @NikBougalis Heh - 谢谢,但最终是徒劳的;我认为那里有一些承诺,但它很快就变得非常毛茸茸……有趣的消遣,寿。 ;)
  • 恭喜获得赏金!正如您所提到的,我们并没有真正接近分配表。这需要内核级访问、分配表和相关文件的排他锁,并且可能需要不断更新以确保新版本的 Windows 不会破坏它。我记得在过去的 8 位时代,一切都是开放的,这会很容易,就像四十行一样。正如你所说,一个有趣的消遣,我很高兴它得到了如此多的关注。也许这是提供插入数据 API 的提示,尤其是在视频编辑领域!
  • @YiminRong 哈,我都没注意到 :) 你说得对,这里还有很多事情要做;我计划保留此收藏夹,并在我有时间扩展时(至少在我达到 SO 限制之前)回来使用它
【解决方案4】:

您不需要(也可能不能)修改文件访问表。您可以使用过滤器驱动程序或可堆叠的 FS 来实现相同的目的。让我们考虑一个 4K 的集群大小。我只是出于我在最后解释的原因写出设计。

  1. 新文件的创建将在标题中显示文件的布局图。标题将提及条目数和条目列表。标头的大小将与集群的大小相同。为简单起见,让标题为固定大小的 4K 条目。例如,假设有一个 20KB 的文件,标题可能会提到:[DWORD:5][DWORD:1][DWORD:2][DWORD:3][DWORD:4][DWORD:5]。此文件目前没有插入内容。

  2. 假设有人在扇区 3 之后插入了一个簇。您可以将其添加到文件末尾并将布局图更改为:[5][1][2][3][5][6 ][4]

  3. 假设有人需要寻找聚类 4。您需要访问布局图并计算偏移量,然后寻找它。它将在前 5 个集群之后,因此将从 16K 开始。

  4. 假设有人连续读取或写入文件。读取和写入必须以相同的方式映射。

  5. 假设标题只剩下一个条目:我们需要通过在文件末尾使用与上面其他指针相同的格式指向新簇的指针来扩展它。要知道我们有多个集群,我们需要做的就是查看项目的数量并计算存储它所需的集群数量。

您可以在 Windows 上使用过滤器驱动程序或在 Linux 上使用可堆栈文件系统 (LKM) 来实现上述所有功能。实现基本级别的功能是在困难的研究生学校迷你项目的水平上。让它作为商业文件系统工作可能非常具有挑战性,尤其是因为您不想影响 IO 速度。

请注意,上述过滤器不会受到磁盘布局/碎片整理等任何更改的影响。如果您认为有帮助,您也可以对自己的文件进行碎片整理。

【讨论】:

  • 这一切都很好,很好,很好,只要生成的文件可以在任何不包含您假设的过滤器/驱动程序或可堆叠FS的系统上原封不动地读取,并且我不必在第一次进行转换时完全读取文件,因为这会使我从所述过滤器驱动程序或可堆叠 FS 中获得的任何好处变得毫无意义。
  • 让它适用于所有系统是微不足道的。它仅取决于过滤器驱动程序的顺序。当您查看另一个系统上的文件时,您将 a) 将文件复制到临时存储(USB 记忆棒等)并稍后打开它或 b) 在网络上安装现有分区。对于 (a) 不需要更改,而对于 (b) CIFS/SMB/NFS 必须覆盖此过滤器。然而,让它在现有文件上工作并非易事,但如果你在 Windows 上使用流,则可以做到这一点。标头可以在流上分配,未来的添加也可以进入流中。
  • 顺便说一句,修改属性表/MFT 仅适用于 NTFS,如果将文件移动到 FAT 分区/ext3 等,也会出现同样的问题。
  • 我不控制将使用此文件的所有系统。
  • 我的意思是说,如果你想在所有文件系统上运行它,你使用的任何机制都会遇到麻烦。在 Windows 中也有 ext3 支持,在 NTFS 等中也有压缩文件支持。
【解决方案5】:

没有。您所问的问题在 Windows 中无法直接实现。

这是因为在 Windows 中,文件是逻辑上连续的字节集合,不可能在不覆盖的情况下将字节插入文件中间。

为了理解原因,让我们进行一个思想实验,看看如果可能的话,它意味着什么。

首先,内存映射文件会突然变得复杂得多。如果我们将文件映射到特定地址,然后在其中添加一些额外的字节,这对内存映射意味着什么?现在内存映射是否应该突然移动?如果是这样,不希望它发生的程序会发生什么?

其次,让我们考虑如果两个句柄打开同一个文件,并且一个在该文件的中间插入额外的字节,GetFilePointer 会发生什么。假设进程 A 已打开文件进行读取,进程 B 已打开文件进行读取和写入。

进程 A 想在进行几次读取时保存它的位置,所以它写了一些代码,有点像

DWORD DoAndThenRewind(HANDLE hFile, FARPROC fp){
   DWORD result;
   LARGEINTEGER zero = { 0 };
   LARGEINTEGER li;
   SetFilePointer(hFile, zero, &li, FILE_CURRENT);

   result = fp();

   SetFilePointer(hFile, &li, &li, FILE_BEGIN);
   return result;
}

现在如果进程 B 想在文件中插入一些额外的字节,这个函数会发生什么?好吧,如果我们在进程 A 当前所在的位置之后添加字节,一切都很好 - 文件指针(即从文件开头开始的线性地址)在前后保持不变,一切都很好。

但是,如果我们在进程 A 所在的 before 中添加额外的字节,那么,突然我们捕获的文件指针都未对齐,并且开始发生不好的事情。

或者换一种说法,在文件中间添加字节意味着我们突然需要发明更聪明的方法来描述我们在文件中的位置以用于倒带,因为文件不再是逻辑上连续的选择字节。

所以到目前为止,我们已经讨论了为什么 Windows 公开这种功能可能是个坏主意;但这并不能真正回答“它实际上是否可能”的问题。这里的答案仍然是否定的。这是不可能的。

为什么?因为没有这样的功能暴露给用户模式程序来执行此操作。作为一个用户模式程序,你有一个获取文件句柄的机制(NtCreateFile/NtOpenFile),你可以通过 NtReadFile/NtWriteFile 读取和写入它,你可以通过 NtSetFileInformation 查找并重命名和删除它,然后你可以通过 NtClose 释放句柄引用。

即使在内核模式下,您也没有更多选择。文件系统 API 是从您那里抽象出来的,文件系统将文件视为逻辑上连续的字节集合,而不是字节范围的链接列表或任何可以轻松公开一种方法以便您在中间插入非覆盖字节的方法一个文件。

这并不是说这是不可能的本身。正如其他人所提到的,您可以打开磁盘本身,伪装成 NTFS 并直接更改分配给特定 FCB 的磁盘集群。但这样做是勇敢的。 NTFS 几乎没有文档记录,很复杂,可能会发生变化,并且即使它没有被操作系统挂载也很难修改,不管它什么时候挂载。

所以答案恐怕是。不可能通过正常的安全 Windows 机制在文件中间添加额外字节作为插入而不是覆盖操作。

相反,请考虑查看您的问题,看看是否适合将文件分块为较小的文件并拥有一个索引文件。这样您就可以修改索引文件以插入额外的块。通过打破对需要驻留在一个文件中的数据的依赖,您会发现更容易避免文件系统要求文件是逻辑上连续的字节集合。然后,您将能够修改索引文件以向“pseduofile”添加额外的块,而无需将整个伪文件读入内存。

【讨论】:

  • 我的要求不会改变;我仍然需要一种将簇插入文件中心的方法,您已经规定这是可能的。我已经知道注意事项了,所以如果你仍然想参与这个问题,把你的精力集中在弄清楚如何做,而不是为什么不能做。谢谢。
  • 投反对票的不是我,顺便说一句。
  • @RobertHarvey:我没有说可以通过 Windows 将集群添加到文件中间。如果您真的想通过共同安装 NTFS 来编写自己的文件系统驱动程序,您可以编写自己的文件系统驱动程序。但是,当您这样做时,您并不是通过 Windows 进行的(这就是磁盘碎片整理程序的工作方式——尽管它们具有由编写文件系统的同一个人编写的好处)。无法从 Windows 内部向文件中间插入额外的字节,因为这样做违反了 Windows 文件系统对文件的抽象,即逻辑上连续的字节集合。
  • @RobertHarvey:我的最后一段还告诉你如何将你的问题重组为一种允许你做同样事情的方式;也就是说,为了打破 Windows 文件系统的限制,你需要打破对它的依赖。如果您将文件制作成带有索引文件和一系列“叶”文件的一系列文件,那么插入额外字节变得很容易添加新叶并更新索引。没有内核模式驱动程序、解析 NTFS 或反对它根本无法在所需的 Windows 文件系统上完成的事实。
  • +1 这个答案似乎与(或多或少)“接受”的答案一致(凭借赏金)。不理解反对票。
【解决方案6】:

您知道在未对齐的位置插入未对齐的数据几乎 99.99% 是不可能的吗? (也许可以使用一些基于压缩的 hack。)我认为你可以。

“最简单”的解决方案是创建稀疏运行记录,然后覆盖稀疏范围。

  1. 对 NTFS 缓存做一些事情。最好在脱机/卸载驱动器上执行操作。
  2. 获取文件记录(@JerKimball 的回答听起来很有帮助,但没有提供)。 如果文件中的属性溢出并被存储起来,则可能会出现问题。
  3. 获取文件的数据运行列表。此处描述了数据运行的概念和格式 (http://inform.pucp.edu.pe/~inf232/Ntfs/ntfs_doc_v0.5/concepts/data_runs.html),其他一些 NTFS 格式数据可以在相邻页面上看到。
  4. 遍历数据运行,累积文件长度,以找到正确的插入点。
  5. 您很可能会发现插入点位于运行的中间。您需要拆分运行,这并不难。 (暂时将两个结果运行存储起来。)
  6. 创建稀疏运行记录非常容易。它只是由字节前置的运行长度(以簇为单位),其中包含长度的低 4 位中的字节大小(高 4 位应为零以指示备用运行)。
  7. 现在您需要计算必须在数据运行列表中插入多少额外字节,以某种方式为它们让路并执行插入/替换。
  8. 然后你需要修复文件大小属性使其与运行一致。
  9. 最后您可以挂载驱动器并将插入的信息写入备用位置。

【讨论】:

    【解决方案7】:

    这完全取决于最初的问题是什么,这就是您要实现的目标。修改 FAT / NTFS 表不是问题,它是您问题的解决方案 - 可能是优雅和高效的,但更可能是非常危险和不合适的。您提到您无法控制将使用它的用户系统,因此可能至少对于其中一些用户,管理员会反对入侵文件系统内部。

    无论如何,让我们回到问题上来。鉴于信息不完整,可能会想象出几个用例,根据用例的不同,解决方案将是简单的还是困难的。

      1234563即使需要半个小时。我知道这听起来很愚蠢,但这是一个常见的用例——一旦你完成了文件的编辑,你保存它,关闭程序,很长一段时间你都不需要那个文件了。李>
    1. 除非你这样做。也许用户决定再编辑一些,或者另一个用户出现。在这两种情况下,您的应用程序都可以轻松检测到文件正在保存到硬盘中(例如,在保存主文件时,您可能有一个隐藏的保护文件)。在这种情况下,您将按原样打开文件(部分保存),但向用户显示文件的自定义视图,使其看起来好像文件处于最终状态。毕竟,您拥有关于哪些文件块必须移动到哪里的所有信息。

    2. 除非用户需要立即在另一个编辑器中打开文件(这不是很常见的情况,特别是对于非常专业的文件格式,但谁知道呢)。如果是这样,您是否有权访问该其他编辑器的源代码?或者您能否与其他编辑器的开发人员交谈并说服他们将未完全保存的文件视为处于最终状态(这并不难——只需从保护文件中读取偏移信息)。我想其他编辑器的开发人员同样对保存时间过长感到沮丧,并且很乐意接受您的解决方案,因为这将有助于他们的产品。

    3. 我们还能拥有什么?也许用户想立即将文件复制或移动到其他地方。 Microsoft 可能不会为了您的利益而更改 Windows Explorer。在这种情况下,您要么需要实现 UMDF 驱动程序,要么明确禁止用户这样做(例如,重命名原始文件并将其隐藏,在其位置留下空白占位符;当用户至少尝试复制文件时他会知道出了什么问题)。

    4. 如果您事先知道将编辑哪些文件,就会出现另一种不太适合上述层次结构 1-4 的可能性。在这种情况下,您可以“预稀疏”文件,沿文件的体积均匀地插入随机间隙。这是由于您提到的文件格式的特殊性质:如果链接正确指向下一个数据块,则可能没有数据间隙。如果你知道哪些文件将被编辑(不是不合理的假设——你的硬盘驱动器上有多少 10Gb 文件?)你在用户开始编辑之前“膨胀”文件(比如前一天晚上),然后四处走动当您需要插入新数据时,这些较小的数据块。这当然也依赖于您不必插入太多的假设。

    在任何情况下,根据您的用户实际想要什么,答案总是不止一个。但我的建议来自设计师的角度,而不是程序员的角度。

    【讨论】:

      【解决方案8】:

      已编辑 - 另一种方法 - 为这项任务切换到 Mac 怎么样?他们拥有卓越的编辑能力和自动化能力!

      已编辑 - 原始规范表明该文件被修改了很多,而是修改了一次。建议像其他人指出的那样在后台进行操作:复制到新文件,删除旧文件,将新文件重命名为旧文件。

      我会放弃这种方法。数据库就是你要找的。/YR

      【讨论】:

      • 数据库有什么帮助?
      • 您正在查看大量需要经常修改的信息。如果信息是结构化的,即使是不透明的二进制 blob,您也可以修改它的任何部分,而不管文件结构如何。就这么简单:
      • 我正在查看一个大的二进制文件,文件开头的一小部分需要修改一次。 将其粘贴到数据库中需要我读取整个文件,这是我试图避免的。
      • 好的,这还不清楚。建议在后台做一个完整的复制操作,删除旧文件,然后将新文件重命名为旧文件。它是最安全的,甚至可以移植到其他操作系统。
      • 是的。这是你想要什么和你需要什么之间的区别。你想要的东西对你和其他所有应用程序都是危险的,即使在不同的 HD 或 MS Windows 操作系统版本中也是不可移植的。接受的“解决方案”可能会工作五分钟。我处于安全状态,您最终需要的代码将通过大量病毒扫描程序引发危险信号。真的,重新考虑方法,它会为您省去很多麻烦。
      【解决方案9】:

      还有一种可能。

      创建一个用户模式文件系统,例如使用 FUSE 或 Dokan,并且您设计为保存单个文件。从那里,您可以使用任何您想要的解决方案,包括将多个文件的片段连接在一起,使某些东西看起来好像是一个大文件。

      然后创建指向该文件的符号链接。

      【讨论】:

        猜你喜欢
        • 2017-06-16
        • 1970-01-01
        • 2018-11-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多