30

ファイルの途中にいくつかのファイル クラスタを挿入して、データを挿入する方法が必要です。

通常、ファイル全体を読み込んで変更を書き戻すだけですが、ファイルのサイズは数ギガバイトであり、ファイルを読み込んで書き戻すだけで 30 分かかります。

クラスターのサイズは気にしません。基本的に、挿入したクラスターの最後にゼロを書き出すことができ、このファイル形式でも機能します。

Windows ファイル API (またはその他のメカニズム) を使用して、ファイルのファイル アロケーション テーブルを変更し、ファイルの途中の指定されたポイントに 1 つ以上の未使用のクラスターを挿入するにはどうすればよいですか?

4

9 に答える 9

26

[編集:]

Blah-私は「これは、少なくとも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_NTFSstructこれは、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;
}

これで、mess'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);
    }
}
于 2013-03-14T01:03:32.030 に答える
7

ロバート、あなたが達成したいことは、その音からマウントされているファイルシステムのファイルシステムデータ構造を積極的に操作せずに実際に行うことができるとは思いません. この種の運動がどれほど危険賢明でないかを言う必要はないと思います.

しかし、それを行う必要がある場合は、開始するために「ナプキンの裏のスケッチ」を提供できると思います。

LCN/VCN マッピングを微調整することで、NTFS の「スパース ファイル」サポートを利用して、単純に「ギャップ」を追加できます。完了したら、ファイルを開き、新しい場所を探してデータを書き込みます。NTFS は透過的にスペースを割り当て、ファイルの途中にデータを書き込みます。

詳細については、NTFS での最適化サポートに関するこのページを参照して、物事を少し操作し、ファイルの途中にクラスターを挿入できるようにする方法のヒントを確認してください。少なくとも、認可された API をこの種の目的で使用することにより、ファイルシステムを修復できないほど破損させる可能性は低くなりますが、それでも恐ろしくファイルをホースすることはできると思います。

必要なファイルの検索ポインターを取得し、必要な場所でそれらを分割して、必要なだけ余分なスペースを追加し、ファイルを移動します。Russinovich/Ionescu の "Windows Internals" 本 ( http://www.amazon.com/Windows%C2%AE-Internals-Inclusive-Windows-Developer/dp/0735625301 )には、この種の興味深い章があります。

于 2013-03-13T22:00:15.403 に答える
2

ファイル アクセス テーブルを変更する必要はありません (おそらく変更することもできません)。フィルタードライバーまたはスタック可能な FS を使用して同じことを実現できます。4K のクラスタ サイズを考えてみましょう。最後に説明する理由から、単にデザインを書き出すだけです。

  1. 新しいファイルの作成は、ヘッダー内のファイルのレイアウト マップになります。ヘッダーには、エントリの数とエントリのリストが記載されます。ヘッダーのサイズは、クラスターのサイズと同じになります。簡単にするために、ヘッダーは 4K エントリの固定サイズにします。たとえば、ヘッダーに [DWORD:5][DWORD:1][DWORD:2][DWORD:3][DWORD:4][DWORD:5] と記載されている 20KB のファイルがあるとします。このファイルには現在挿入がありません。

  2. 誰かがセクター 3 の後にクラスターを挿入するとします。それをファイルの末尾に追加し、レイアウト マップを [5][1][2][3][5][6][4] に変更できます。

  3. 誰かがクラスタ 4 をシークする必要があるとします。レイアウト マップにアクセスし、オフセットを計算してからシークする必要があります。最初の 5 つのクラスタの後になるため、16K から開始されます。

  4. 誰かがファイルをシリアルに読み書きするとします。読み取りと書き込みは同じ方法でマッピングする必要があります。

  5. ヘッダーにもう 1 つのエントリしか残っていないとします。上記の他のポインターと同じ形式を使用して、ファイルの最後に新しいクラスターへのポインターを配置することにより、ヘッダーを拡張する必要があります。複数のクラスターがあることを知るには、アイテムの数を見て、それを格納するために必要なクラスターの数を計算するだけです。

Windows ではフィルター ドライバーを使用するか、Linux ではスタック可能なファイル システム (LKM) を使用して、上記のすべてを実装できます。基本レベルの機能を実装することは、大学院のミニ プロジェクトの難しさのレベルにあります。これを商用ファイルシステムとして機能させることは、特に IO 速度に影響を与えたくないため、非常に困難な場合があります。

上記のフィルターは、ディスク レイアウトの変更や最適化などの影響を受けないことに注意してください。役立つと思われる場合は、独自のファイルを最適化することもできます。

于 2013-03-14T01:48:17.897 に答える
2

いいえ。あなたが求めていることは、Windows では直接可能ではありません。

これは、Windows では、ファイルが論理的に連続したバイトの集まりであり、上書きせずにファイルの途中にバイトを挿入することができないためです。

その理由を理解するために、それが可能であるとしたら、それが何を意味するかについての思考実験を行ってみましょう。

第 1 に、メモリ マップ ファイルが突然、より複雑になります。ファイルを特定のアドレスにマップし、その中間に余分なバイトを配置した場合、メモリ マッピングはどうなるでしょうか? メモリ マッピングが突然移動する必要がありますか? もしそうなら、それを期待していないプログラムはどうなりますか?

次に、同じファイルに対して 2 つのハンドルが開いていて、1 つのハンドルがそのファイルの途中に余分なバイトを挿入した場合に、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 の前に余分なバイトを追加すると、突然、キャプチャされたファイル ポインターがすべてずれて、悪いことが起こり始めます。

別の言い方をすれば、ファイルの途中にバイトを追加するということは、ファイルが論理的に連続したバイトの選択ではなくなるため、巻き戻しのためにファイル内のどこにいるかを記述するためのより巧妙な方法を突然考案する必要があることを意味します。

したがって、これまで、Windows がこの種の機能を公開することがおそらく悪い考えである理由について説明してきました。しかし、それは「実際に可能か」という質問には実際には答えません。ここでの答えはまだノーです。それは不可能。

なんで?これを行うために、そのような機能がユーザー モード プログラムに公開されていないためです。ユーザーモード プログラムとして、ファイルへのハンドルを取得するためのメカニズム (NtCreateFile/NtOpenFile) が 1 つあり、NtReadFile/NtWriteFile を介してファイルの読み取りと書き込みを行うことができ、NtSetFileInformation を介してファイルを検索し、名前を変更して削除することができます。 NtClose を介してハンドル参照を解放できます。

カーネル モードからでも、これ以上のオプションはありません。ファイルシステム API は抽象化されており、ファイルシステムはファイルを論理的に連続したバイトのコレクションとして扱います。バイト範囲のリンクされたリストや、途中で非上書きバイトを挿入するメソッドを簡単に公開できるものとしてではありません。ファイル。

それ自体が不可能だと言っているわけではありません。他の人が述べたように、ディスク自体を開き、NTFS のふりをして、特定の FCB に割り当てられたディスク クラスターを直接変更することが可能です。しかし、そうするのは勇敢です。NTFS はほとんど文書化されておらず、複雑で、変更される可能性があり、OS によってマウントされていない場合でも変更が困難です。

ですから、答えはノーです。通常の安全な Windows メカニズムでは、上書き操作ではなく挿入としてファイルの途中に余分なバイトを追加することはできません。

代わりに、問題を調べて、ファイルを小さなファイルに分割し、インデックス ファイルを作成することが適切かどうかを検討してください。そうすれば、インデックス ファイルを変更して余分なチャンクを挿入できます。1 つのファイルに存在する必要があるデータへの依存を断ち切ることで、ファイルが論理的に連続したバイトのコレクションであるというファイル システムの要件を回避しやすくなります。その後、疑似ファイル全体をメモリに読み込まなくても、インデックス ファイルを変更して「疑似ファイル」に余分なチャンクを追加できます。

于 2013-03-14T20:44:39.377 に答える
2

抽象的な質問、抽象的な答え:

FAT でこれを行うことは確かに可能であり、おそらく他のほとんどの FS では、より一般的な最適化プロセスではなく、本質的にファイルを断片化することになります。

FAT は、データが保存されるクラスター番号のチェーンを生成するクラスター ポインターの周りで編成されます。最初のリンク インデックスはファイル レコードと共に保存され、2 番目のリンク インデックスはインデックス [最初のリンクの番号] の割り当てテーブルに保存されます。挿入するデータがクラスターの境界で終了する限り、チェーンのどこにでも別のリンクを挿入できます。

オープン ソース ライブラリを見つけることで、C でこれを行うのがはるかに簡単になる可能性があります。PInvoke を使用して C# でこれを行うことはおそらく可能ですが、開始するのに適したサンプル コードがあちこちに見つかりません。

ファイル形式 (ビデオ ファイル?) を制御できないのではないかと思いますが、そうすると、そもそも問題を回避するようにデータ ストレージを設計する方がはるかに簡単になります。

于 2013-03-13T21:31:27.187 に答える
1

それはすべて、元の問題が何であるか、つまり達成しようとしているものに依存します。FAT / NTFS テーブルの変更は問題ではなく、問題の解決策です。潜在的にエレガントで効率的ですが、非常に危険で不適切である可能性が高くなります。あなたはそれが使用されるユーザーのシステムを制御できないと述べたので、おそらく少なくともそれらのいくつかについては、管理者はファイルシステムの内部へのハッキングに反対するでしょう.

とにかく、問題に戻りましょう。不完全な情報を考えると、いくつかのユースケースが想像でき、ユースケースに応じて解決策が簡単または困難になります。

  1. 編集後、ファイルがしばらく必要ないことがわかっている場合は、編集内容を 0.5 秒で保存するのは簡単です。ウィンドウを閉じて、アプリケーションがバックグラウンドで保存を完了するまで待ちます。時間。ばかげているように聞こえるかもしれませんが、これは頻繁に使用されるケースです。ファイルの編集が完了したら、ファイルを保存してプログラムを閉じると、そのファイルはもう長期間必要ありません。

  2. あなたがしない限り。ユーザーがさらに編集することを決定するか、別のユーザーが来る可能性があります。どちらの場合でも、アプリケーションは、ファイルがハードディスクに保存されていることを簡単に検出できます (たとえば、メイン ファイルの保存中に隠しガード ファイルが存在する可能性があります)。この場合、ファイルをそのまま (部分的に保存) で開きますが、ユーザーにはファイルのカスタマイズされたビューを提示し、ファイルが最終状態にあるかのように見せます。結局のところ、ファイルのどのチャンクをどこに移動する必要があるかについてのすべての情報が得られます。

  3. ユーザーがすぐに別のエディターでファイルを開く必要がある場合を除きます (これは、特に非常に特殊なファイル形式の場合、あまり一般的ではありませんが、誰にもわかりません)。もしそうなら、その他のエディタのソースコードにアクセスできますか? または、他のエディターの開発者と話し、不完全に保存されたファイルを最終状態であるかのように扱うように説得できます (それほど難しいことではありません。ガード ファイルからオフセット情報を読み取るだけです)。他のエディターの開発者も同様に長い保存時間に不満を感じており、製品に役立つのであなたのソリューションを喜んで受け入れると思います.

  4. 他に何がありますか?おそらく、ユーザーはすぐにファイルを別の場所にコピーまたは移動したいと考えています。Microsoft はおそらく、ユーザーの利益のために Windows Explorer を変更することはありません。その場合、UMDF ドライバーを実装するか、ユーザーがそうすることを明確に禁止する必要があります (たとえば、元のファイルの名前を変更して非表示にし、その場所に空白のプレースホルダーを残します。ユーザーが少なくともファイルをコピーしようとすると、彼は何かがうまくいかなかったことを知るでしょう)。

  5. 上記の階層 1 ~ 4 にうまく収まらない別の可能性は、どのファイルが編集されるかを事前に知っている場合に発生します。その場合、ファイルのボリュームに沿って均一にランダムなギャップを挿入して、ファイルを「事前にまばらにする」ことができます。これは、あなたが言及したファイル形式の特殊な性質によるものです。リンクが次のデータ チャンクを正しく指している場合、データがないギャップが存在する可能性があります。編集されるファイルがわかっている場合 (不当な仮定ではなく、ハード ドライブの周りに 10Gb ファイルがいくつあるか?)、ユーザーが編集を開始する前に (たとえば、前の晩に) ファイルを「膨張」させてから、移動するだけです。新しいデータを挿入する必要がある場合は、これらの小さなデータのチャンク。もちろん、これはあまりにも多くを挿入する必要がないという前提にも依存しています。

いずれにせよ、ユーザーが実際に何を望んでいるかに応じて、常に複数の答えがあります。しかし、私のアドバイスは、プログラマーの視点ではなく、デザイナーの視点からのものです。

于 2013-03-19T05:04:45.067 に答える
1

アライメントされていない場所にアライメントされていないデータを挿入することはほぼ 99.99% 不可能であることを理解していますか? (おそらく、圧縮に基づくハックを使用できます。) そうだと思います。

「最も簡単な」解決策は、疎な実行レコードを作成してから、疎な範囲を上書きすることです。

  1. NTFSキャッシュで何かをしてください。オフライン/マウントされていないドライブで操作を実行することをお勧めします。
  2. ファイル レコードを取得します (@JerKimball の回答は役に立ちますが、それだけではありません)。ファイルが属性でオーバーフローして格納されている場合、問題が発生する可能性があります。
  3. ファイルのデータ実行リストにアクセスします。データ実行の概念と形式はここ ( http://inform.pucp.edu.pe/~inf232/Ntfs/ntfs_doc_v0.5/concepts/data_runs.html ) で説明されており、その他の NTFS 形式のデータは隣接するページで見ることができます。 .
  4. データの実行を繰り返し、ファイルの長さを累積して、正しい挿入スポットを見つけます。
  5. ほとんどの場合、挿入ポイントがランの途中にあることがわかります。実行を分割する必要がありますが、これは難しくありません。(今のところ、結果として得られた 2 つの実行を保存しておいてください。)
  6. スパース実行レコードの作成は非常に簡単です。これは、バイトが先頭に追加されたランの長さ (クラスター単位) であり、下位 4 ビットに長さのバイト サイズが含まれます (予備のランを示すには、上位 4 ビットをゼロにする必要があります)。
  7. ここで、データランリストに挿入する必要がある追加のバイト数を計算し、何らかの形でそれらのために道を開けて、挿入/置換を行う必要があります。
  8. 次に、実行と一致するようにファイル サイズ属性を修正する必要があります。
  9. 最後に、ドライブをマウントし、挿入された情報をスペア スポットに書き込むことができます。
于 2013-03-16T00:06:19.860 に答える
-2

編集済み - 別のアプローチ - このタスクのために Mac に切り替えてみませんか? 自動化機能を備えた優れた編集機能を備えています。

編集済み - 元の仕様では、ファイルが何度も変更されていることが示唆されていましたが、1 回変更されています。バックグラウンドで操作を行うことを他の人が指摘しているように提案します: 新しいファイルにコピー、古いファイルを削除、新しいファイルの名前を古いファイルに変更します。

私はこのアプローチを放棄します。データベースはあなたが探しているものです。/YR

于 2013-03-19T17:53:27.253 に答える