Подтвердить что ты не робот

Вставить байты в середину файла (в файловой системе Windows) без чтения всего файла (используя таблицу распределения файлов)?

Мне нужен способ вставить некоторые кластеры файлов в середину файла, чтобы вставить некоторые данные.

Обычно я просто читал весь файл и записывал его обратно с изменениями, но файлы имеют размер в несколько гигабайт, и для чтения файла требуется всего 30 минут. Запишите его снова.

Размер кластера меня не беспокоит; Я могу по существу записать нули до конца моих вставленных кластеров, и он все равно будет работать в этом формате.

Как использовать API файлов Windows (или какой-либо другой механизм), чтобы изменить таблицу размещения файлов в файле, вставив один или несколько неиспользуемых кластеров в указанную точку посередине файла?

4b9b3361

Ответ 1

[EDIT:]

Бла - Я собираюсь сказать: "Это не выполнимо, по крайней мере, не с помощью модификации MFT, без БОЛЬШОЙ боли"; Вначале структуры NTFS MFT не являются на 100% "открытыми", поэтому я начинаю вникать в область реверсивного проектирования, что имеет юридические последствия, с которыми я не настроен. Кроме того, выполнение этого в .NET - это процесс утомительного процесса сопоставления и сортировки структур, основанный на множестве догадок (и не заставляйте меня начинать с того, что большинство структур MFT сжаты странным образом). Короткий рассказ, в то время как я очень много узнал о том, как работает NTFS, я не ближе к решению этой проблемы.

[/EDIT]

Ух... так много маршаллинга ерунда....

Это показалось мне "интересным", поэтому я был вынужден зацикнуться на проблеме... он все еще был "ответом в процессе", но хотел опубликовать то, что все, что я должен был, помогать другим в приходе с чем-то.:)

Кроме того, у меня есть грубое ощущение, что это будет FAR проще на FAT32, но, учитывая, что у меня есть только NTFS для работы с...

Итак - лоты для создания пикинга и сортировки, поэтому давайте начнем там и работаем назад:

Как можно было бы предположить, стандартный .NET File/IO apis не поможет вам здесь - нам нужен доступ на уровне устройства:

[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));

Whoa, whoa whoa - что это за 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;
}

Итак, теперь мы можем отобразить целое mess'o'bytes обратно в эту структуру:

// 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();
}

Argh, более нативные структуры для обсуждения - поэтому 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();
        }
    }
}

И... здесь, где я сейчас заглядываю; главным образом потому, что я хочу пообедать и тому подобное. Я вернусь к этому, однако!

Ссылки (частично для моей собственной памяти, частично для помощи другим исследователям)

Сброс полного кода a'следующий:

Все нативные отображения, которые я просматривал выше (из-за ограничений размера сообщения, а не полного перефразирования):

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);
    }
}

Ответ 2

Роберт, я не думаю, что то, чего вы хотите достичь, действительно невозможно обойтись без активного манипулирования файловыми структурами файловой системы для файловой системы, которая, по звукам ее, монтируется. Я не думаю, что должен сказать вам, насколько опасно и неразумно это упражнение.

Но если вам нужно это сделать, я думаю, я могу дать вам "эскиз на обратной стороне салфетки", чтобы вы начали:

Вы можете использовать поддержку "разреженного файла" NTFS, чтобы просто добавить "пробелы", изменив сопоставления LCN/VCN. Как только вы это сделаете, просто откройте файл, найдите новое место и напишите свои данные. NTFS будет прозрачно распределять пространство и записывать данные в середине файла, где вы создали отверстие.

Для получения дополнительной информации посмотрите на поддержку дефрагментации в NTFS, чтобы узнать, как вы можете немного манипулировать вещами и позволить вам вставлять кластеры в середине файла. По крайней мере, используя санкционированный API для такого рода вещей, вы вряд ли испортите файловую систему без ремонта, хотя, по-моему, вы все еще можете ужасно вставить свой файл.

Получите указатели поиска для нужного файла, разделите их там, где вам нужно, чтобы добавить столько свободного места, сколько вам нужно, и переместите файл. Там интересная глава по этому поводу в книге Русиновича/Ионеску "Windows Internals" (http://www.amazon.com/Windows%C2%AE -Internals, в том числе-Windows-разработчиков/др/0735625301)

Ответ 3

Абстрактный вопрос, абстрактный ответ:

Конечно, это можно сделать в FAT и, вероятно, в большинстве других FS, вы по существу будете фрагментировать файл, а не более общий процесс дефрагментации.

FAT организован вокруг указателей кластера, которые создают цепочку номеров кластеров, где хранятся данные, первый индекс ссылки хранится с записью файла, второй хранится в таблице распределения по индексу [номер первой ссылки] и т.д. Возможно вставить другую ссылку в любом месте цепи, пока данные, которые вы вставляете, заканчиваются на границе кластера.

Скорее всего, вам будет легче сделать это в C, найдя библиотеку с открытым исходным кодом. Хотя, возможно, это возможно сделать в С# с PInvoke, вы не найдете хороший образец кода, плавающий вокруг, чтобы вы начали.

Я подозреваю, что у вас нет никакого контроля над файловым форматом (видеофайлы?), если вы это сделаете, было бы намного проще создать хранилище данных, чтобы избежать проблемы в первую очередь.

Ответ 4

Нет. То, что вы запрашиваете, прямо не возможно в 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;
}

Теперь, что происходит с этой функцией, если Process B хочет вставить в файл некоторые дополнительные байты? Ну, если мы добавим байты после того, где в настоящий момент выполняется процесс A, все в порядке - указатель файла (который является линейным адресом от начала файла) остается прежним до и после, и все хорошо.

Но если мы добавим лишние байты до того, как процесс A будет, ну, вдруг наши захваченные указатели файлов будут смещены, и начнутся плохие вещи.

Или, говоря иначе, добавление байтов в середину файла означает, что нам внезапно нужно изобретать более умные способы описания того, где мы находимся в файле для перемотки, поскольку файлы больше не являются логически смежным выбором байтов.

Итак, до сих пор мы обсуждали, почему, вероятно, плохая идея для Windows выявить такую ​​функциональность; но это на самом деле не отвечает на вопрос "действительно ли это возможно". Ответ здесь по-прежнему нет. Это невозможно.

Почему? Потому что такая функциональность не доступна для программ пользовательского режима для этого. В качестве программы пользовательского режима у вас есть один механизм для получения дескриптора файла (NtCreateFile/NtOpenFile), вы можете читать и записывать его через NtReadFile/NtWriteFile, вы можете искать его и переименовывать и удалять его через NtSetFileInformation, и вы может освободить ссылку на обращение через NtClose.

Даже в режиме ядра у вас не так много других опций. API-интерфейс файловой системы абстрагируется от вас, а файловые системы обрабатывают файлы как логически смежные коллекции байтов, а не как связанные списки диапазонов байтов или что-либо, что упростило бы выставить метод для вставки неиспользуемых байтов в середине файл.

Это не значит, что это невозможно по-своему. Как отмечали другие, вы можете открыть сам диск, притвориться NTFS и напрямую изменить кластеры дисков, назначенные конкретному FCB. Но делать это смело. NTFS едва документирована, является сложной, подверженной изменениям и ее трудно модифицировать, даже если она не установлена ​​ОС, неважно, когда она есть.

Итак, ответ, я боюсь, нет. С помощью обычных безопасных механизмов Windows невозможно добавить дополнительные байты в середину файла в качестве вставки, а не как операцию перезаписи.

Вместо этого рассмотрите свою проблему, чтобы узнать, подходит ли вам для размещения ваших файлов в меньших файлах и иметь индексный файл. Таким образом, вы сможете изменить индексный файл, чтобы вставить дополнительные куски. Разрушая вашу уверенность в данных, которые необходимо проживать в одном файле, вам будет легче избежать требования к файловой системе о том, что файл является логически смежным набором байтов. Затем вы сможете изменить индексный файл, чтобы добавить дополнительные фрагменты в ваш "pseduofile", без необходимости читать весь псевдофайл в памяти.

Ответ 5

Вам не нужно (и, вероятно, не удается) изменить таблицу доступа к файлам. Вы можете добиться того же, используя фильтр-драйвер или штабелируемый FS. Рассмотрим размер кластера 4K. Я просто излагаю дизайн по причинам, которые я объясняю в конце.

  • Создание нового файла будет отображать макет файла в заголовке. В заголовке указывается количество записей и список записей. Размер заголовка будет таким же, как размер кластера. Для простоты пусть заголовок имеет фиксированный размер с 4K элементами. Например, предположим, что есть файл, скажем, 20 Кбайт, который может содержать заголовок: [DWORD: 5] [DWORD: 1] [DWORD: 2] [DWORD: 3] [DWORD: 4] [DWORD: 5]. Этот файл в настоящее время не имеет вставок.

  • Предположим, кто-то вставляет кластер после сектора 3. Вы можете добавить его в конец файла и изменить макет-карту на: [5] [1] [2] [3] [5] [6 ] [4]

  • Предположим, кому-то нужно искать кластер 4. Вам нужно будет получить доступ к макет-карте и рассчитать смещение, а затем обратиться к нему. Это будет после того, как первые 5 кластеров начнутся с 16K.

  • Предположим, что кто-то читает или пишет последовательно файл. Чтения и записи должны будут отображаться одинаково.

  • Предположим, что заголовок имеет еще одну запись: нам нужно расширить его, указав на новый кластер в конце файла, используя тот же формат, что и другие указатели выше. Чтобы знать, что у нас есть более одного кластера, нам нужно всего лишь посмотреть на количество элементов и рассчитать количество кластеров, необходимых для его хранения.

Вы можете реализовать все вышеперечисленное, используя драйвер фильтра в Windows или стекируемую файловую систему (LKM) в Linux. Реализация базового уровня функциональности находится на уровне мини-проекта градиентной школы в трудном положении. Сделать это для работы в качестве коммерческой файловой системы может быть довольно сложной задачей, поскольку вы не хотите влиять на скорости ввода-вывода.

Обратите внимание, что на этот фильтр не влияют изменения в макете/дефрагментации диска и т.д. Вы также можете дефрагментировать свой собственный файл, если считаете, что это будет полезно.

Ответ 6

Понимаете ли вы, что почти 99,99% невозможно вставить несогласованные данные в неприсоединившихся местах? (Возможно, может быть использован какой-то хак, основанный на сжатии.) Я думаю, что вы это делаете.

"Самое простое" решение - создать разреженные записи прогона, а затем писать по разреженным диапазонам.

  • Сделайте что-нибудь с кешем NTFS. Лучше всего выполнять операции на автономном/немонтированном диске.
  • Получить запись файла (ответ @JerKimball звучит полезно, но не подходит). Там могут быть проблемы, если файл переполнен атрибутами, и они сохраняются.
  • Перейдите в список запуска данных файла. Концепция и формат запуска данных описаны здесь (http://inform.pucp.edu.pe/~inf232/Ntfs/ntfs_doc_v0.5/concepts/data_runs.html), а некоторые другие данные формата NTFS можно увидеть на соседних страницах.
  • Итерировать через прогоны данных, накапливая длину файла, чтобы найти правильное место для вставки.
  • Скорее всего, вы найдете, что ваша точка ввода находится в середине прогона. Вам нужно будет разбить пробег, который не сложно. (Просто сохраните два полученных результата на данный момент.)
  • Создание редкой записи запуска очень просто. Это просто длина пробега (в кластерах), добавленная байтом, которая содержит размер байта длины в нем ниже 4 бит (более высокие 4 бита должны быть равны нулю, чтобы указать запасной ход).
  • Теперь вам нужно рассчитать, сколько дополнительных байтов вы должны вставить в список запуска данных, каким-то образом уступить им путь и выполнить вставку/замену.
  • Затем вам нужно исправить атрибут размера файла, чтобы он соответствовал прогонам.
  • Наконец, вы можете подключить накопитель и записать вставленную информацию по запасным точкам.

Ответ 7

Все зависит от исходной проблемы, чего вы пытаетесь достичь. Изменение таблицы FAT/NTFS не является проблемой, это решение вашей проблемы - потенциально элегантное и эффективное, но более вероятно, очень опасное и неуместное. Вы упомянули, что у вас нет контроля над системами пользователей, где он будет использоваться, поэтому, по-видимому, по крайней мере для некоторых из них администратор будет возражать против взлома внутри внутренних систем файловой системы.

В любом случае, вернемся к проблеме. Учитывая неполную информацию, можно представить несколько вариантов использования, и решение будет либо простым, либо трудным в зависимости от варианта использования.

  • Если вы знаете, что после редактирования файл не понадобится в течение некоторого времени, то сохранить редактирование за полсекунды легко - просто закройте окно и пусть приложение закончит сохранение в фоновом режиме, даже если это занимает полчаса. Я знаю, что это звучит глупо, но это частое использование - как только вы закончите редактирование своего файла, вы его сохраните, закройте программу, и вам больше не понадобится этот файл в течение длительного времени.

  • Если вы этого не сделаете. Возможно, пользователь решает изменить еще несколько, или, может быть, другой пользователь приходит. В обоих случаях ваше приложение может легко обнаружить, что файл находится в процессе сохранения на жесткий диск (например, у вас может быть скрытый файл защиты при сохранении основного файла). В этом случае вы откроете файл как есть (частично сохраненный), но представите пользователю настроенное представление файла, которое заставляет его выглядеть так, как будто файл находится в конечном состоянии. В конце концов, у вас есть вся информация о том, какие фрагменты файла должны быть перемещены туда.

  • Если пользователю не нужно открывать файл сразу в другом редакторе (это не очень распространенный случай, особенно для очень специализированного формата файла, но тогда кто знает). Если да, имеете ли вы доступ к исходному коду этого другого редактора? Или вы можете поговорить с разработчиками этого другого редактора и убедить их обрабатывать не полностью сохраненный файл, как если бы он находился в конечном состоянии (это не так сложно - все, что требуется, - это прочитать информацию о смещении из файла защиты). Я бы предположил, что разработчики этого другого редактора в равной степени разочарованы долгой экономией времени и с радостью воспользуются вашим решением, поскольку это поможет их продукту.

  • Что еще мы можем? Возможно, пользователь хочет немедленно скопировать или переместить файл в другое место. Microsoft, вероятно, не изменит Windows Explorer для вашей выгоды. В этом случае вам нужно будет либо реализовать драйвер UMDF, либо просто запретить пользователю делать это (например, переименуйте исходный файл и спрячьте его, оставив на своем месте пустой заполнитель, когда пользователь попытается скопировать файл по крайней мере он узнает, что что-то пошло не так).

  • Еще одна возможность, которая не соответствует приведенной выше иерархии 1-4, подходит, если вы заранее знаете, какие файлы будут редактироваться. В этом случае вы можете "предварительно разрезать" файл, вставляя случайные промежутки равномерно по объему файла. Это связано с особым характером вашего формата файла, который вы упомянули: могут быть пробелы без данных, при условии, что ссылки правильно указывают на следующие следующие фрагменты данных. Если вы знаете, какие файлы будут отредактированы (не необоснованное предположение - сколько 10Gb файлов лежит вокруг вашего жесткого диска?), Вы "раздуваете" файл до того, как пользователь начнет его редактировать (скажем, накануне), а затем просто передвигайтесь эти меньшие куски данных, когда вам нужно вставить новые данные. Это, конечно, также зависит от предположения, что вам не нужно много вкладывать.

В любом случае всегда существует более одного ответа в зависимости от того, что действительно хотят ваши пользователи. Но мой совет исходит от дизайнерской точки зрения, а не от программиста.

Ответ 8

Edited - другой подход - как насчет перехода на Mac для этой задачи? Они имеют превосходные возможности редактирования, с возможностями автоматизации!

Edited - оригинальные спецификации предполагали, что файл сильно модифицировался, вместо этого он модифицируется один раз. Предложите, как другие указали на операцию в фоновом режиме: скопируйте в новый файл, удалите старый файл, переименуйте новый файл в старый файл.

Я бы отказался от этого подхода. База данных - это то, что вы ищете./YR