Insertar bytes en el medio de un archivo (en el sistema de archivos de Windows) sin leer el archivo completo (utilizando la Tabla de asignación de archivos)?


Necesito una forma de insertar algunos clústeres de archivos en el medio de un archivo para insertar algunos datos.

Normalmente, acabo de leer todo el archivo y escribir de vuelta de nuevo con los cambios, pero los archivos son varios gigabytes de tamaño, y se tarda 30 minutos, para leer el archivo y escribir de nuevo.

El tamaño del clúster no me molesta; esencialmente puedo escribir ceros al final de mis clústeres insertados, y todavía funcionará en este formato de archivo.

Cómo ¿usaría la API de archivos de Windows (o algún otro mecanismo) para modificar la Tabla de asignación de archivos de un archivo, insertando uno o más clústeres no utilizados en un punto especificado en el medio del archivo?

Author: Robert Harvey, 2012-11-17

8 answers

[EDITAR:]

Blah - Voy a decir "esto no es factible, al menos no a través de la modificación MFT, sin MUCHO dolor"; en primer lugar, las estructuras MFT NTFS en sí no son 100% "abiertas", por lo que estoy empezando a profundizar en el territorio de ingeniería inversa, que tiene repercusiones legales que no estoy de humor para lidiar con. Además, hacer esto en. NET es un proceso hipertedioso de mapeo y clasificación de estructuras basado en muchas conjeturas (y no me hagas empezar con el hecho de que la mayoría de los MFT las estructuras están comprimidas de formas extrañas). Historia corta, si bien aprendí mucho sobre cómo "funciona" NTFS, no estoy más cerca de una solución a este problema.

[/EDIT]

[15] Ugh...muchas tonterías de clasificación....

Esto me pareció "interesante", por lo tanto me vi obligado a hurgar en el problem...it 's todavía una" respuesta-en-progreso", pero quería publicar lo que todo lo que tenía para ayudar a otros en llegar a algo. :)

También, tengo un sentido áspero que esto sería mucho más fácil en FAT32, pero dado que solo tengo NTFS para trabajar...

So - montones de pinvoking y marshalling, así que empecemos por ahí y trabajemos hacia atrás: {[16]]}

Como uno podría adivinar, las api de archivo/IO estándar. NET no le ayudarán mucho aquí: necesitamos acceso a nivel de dispositivo :

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

Usaremos estas bestias desagradables de win32 así: {[16]]}

// 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 - ¿qué diablos es un BootSector_NTFS? Es un byte mapeado struct que encaja lo más cerca que puedo calcular de cómo se ve la estructura NTFS (FAT32 incluido también):

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

Así que ahora podemos mapear un mess'o'bytes entero a esta estructura: {[16]]}

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

Que en este punto produce:

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

Vamos a confirmar que rápido usando la herramienta de soporte 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)

Genial, parece que estamos en el camino correcto...adelante!

            // 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, más estructuras nativas para discutir-por lo que el MFT está organizado de tal manera que las primeras 16 entradas son "fijo":

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

Donde MFTRecord está:

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

Y...aquí es donde me pierdo por ahora; principalmente porque quiero cenar y eso. ¡Volveré a esto, sin embargo!

Referencias (parcialmente para mi propia memoria, parcialmente para ayudar a otros investigadores)

Completo volcado de código a'following:

Todas las asignaciones nativas que esmalté arriba (debido a limitaciones de tamaño de post, no un refrito completo):

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

Y el arnés de prueba:

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);
    }
}
 23
Author: JerKimball,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-19 15:27:01

Robert, no creo que lo que quieres lograr sea realmente posible sin manipular activamente las estructuras de datos del sistema de archivos para un sistema de archivos que, por sus sonidos, está montado. No creo que tenga que decirte cómo peligroso y imprudente este tipo de ejercicio.

Pero si necesitas para hacerlo, supongo que puedo darte un "boceto en la parte posterior de una servilleta" para comenzar:

Podría aprovechar el soporte de" archivo escaso " de NTFS para simplemente agregue "huecos" ajustando las asignaciones de LCN/VCN. Una vez que lo haga, simplemente abra el archivo, busque la nueva ubicación y escriba sus datos. NTFS asignará el espacio de forma transparente y escribirá los datos en el medio del archivo, donde creó un agujero.

Para obtener más información, consulte esta página sobre soporte de desfragmentación en NTFS para obtener consejos sobre cómo puede manipular las cosas un poco y permitirle insertar clústeres en el medio del archivo. Al menos mediante el uso de la API sancionada para este tipo de cosa, es poco probable que corrompa el sistema de archivos más allá de la reparación, aunque todavía puede manguera horriblemente su archivo, supongo.

Obtenga los punteros de recuperación para el archivo que desea, divídalos donde lo necesite, para agregar tanto espacio adicional como necesite y mueva el archivo. Hay un capítulo interesante sobre este tipo de cosas en el libro "Windows Internals" de Russinovich/Ionescu (http://www.amazon.com/Windows%C2%AE-Internals-Including-Windows-Developer/dp/0735625301)

 7
Author: Nik Bougalis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-13 22:00:15

Pregunta abstracta, respuesta abstracta:

Ciertamente es posible hacer esto en FAT y probablemente en la mayoría de los otros FS, esencialmente estaría fragmentando el archivo, en lugar del proceso más común de desfragmentar.

El FAT se organiza alrededor de punteros de clúster que producen una cadena de números de clúster donde se almacenan los datos, el primer índice de enlace se almacena con el registro de archivos, el segundo se almacena en la tabla de asignación en el índice [número del primer enlace], etc. Es es posible insertar otro enlace en cualquier parte de la cadena, mientras los datos que está insertando terminen en el límite de un clúster.

Es probable que tenga mucho más fácil hacer esto en C al encontrar una biblioteca de código abierto . Si bien es probable que sea posible hacerlo en C# con PInvoke, no encontrará ningún buen código de ejemplo flotando para que pueda comenzar.

Sospecho que usted no tiene ningún control sobre el formato de archivo(archivos de vídeo?), si lo haces sería mucho más fácil de diseñar su almacenamiento de datos para evitar el problema en primer lugar.

 2
Author: Sten Petrov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-13 21:31:27

No. Lo que estás pidiendo no es directamente posible en Windows.

Esto se debe a que en Windows, los archivos son una colección lógicamente contigua de bytes, y no es posible insertar bytes en el medio del archivo sin sobrescribir.

Para entender por qué, llevemos a cabo un experimento mental de lo que significaría si fuera posible.

En primer lugar, los archivos mapeados en memoria de repente se volverían mucho más complicados. Si hemos mapeado un archivo en una dirección en particular, y luego poner algunos bytes adicionales en el medio de ella, ¿qué significaría eso para el mapeo de memoria? ¿Debería el mapeo de memoria ahora moverse repentinamente? Y si es así, ¿qué pasa con el programa que no espera que lo haga?

En segundo lugar, consideremos lo que sucede con GetFilePointer si dos controladores están abiertos al mismo archivo, y uno inserta bytes adicionales en el medio de ese archivo. Supongamos que el Proceso A tiene el archivo abierto para lectura, y el Proceso B lo tiene abierto para lectura y escritura.

Proceso A quiere para guardar su ubicación mientras hace unas cuantas lecturas, por lo que escribe un poco de código como

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

Ahora, ¿qué pasa con esta función si el Proceso B quiere insertar algunos bytes adicionales en el archivo? Bueno, si agregamos los bytes después de donde se encuentra actualmente el proceso A, todo está bien: el puntero del archivo (que es la dirección lineal desde el inicio del archivo) sigue siendo el mismo antes y después y todo está bien.

Pero si agregamos bytes adicionales en antes de donde el proceso A es, bueno, de repente nuestro los punteros de archivos capturados están desalineados y comienzan a suceder cosas malas.

O para decirlo de otra manera, agregar bytes en el medio del archivo significa que de repente necesitamos inventar formas más inteligentes de describir dónde estamos en el archivo para propósitos de rebobinado, ya que los archivos ya no son una selección lógicamente contigua de bytes.

Así que hasta ahora hemos discutido por qué es probablemente una mala idea para Windows exponer este tipo de funcionalidad; pero eso realmente no responde a la pregunta "es realmente posible". La respuesta aquí sigue siendo no. No es posible.

¿Por qué? Debido a que no se expone dicha funcionalidad a los programas de modo de usuario para hacer esto. Como programa en modo usuario, tiene un mecanismo para obtener un identificador de un archivo (NtCreateFile/NtOpenFile), puede leer y escribir en él a través de NtReadFile / NtWriteFile, puede buscarlo y cambiarle el nombre y eliminarlo a través de NtSetFileInformation, y puede liberar la referencia del identificador a través de NtClose.

Incluso desde el modo kernel, no tengo muchas más opciones. La API del sistema de archivos se abstrae lejos de usted, y los sistemas de archivos tratan los archivos como colecciones lógicamente contiguas de bytes, no como listas enlazadas de rangos de bytes o cualquier cosa que haría que sea fácil exponer un método para insertar bytes que no sobrescriban en el medio de un archivo.

Eso no quiere decir que no sea posible per-se. Como otros han mencionado, es posible que abras el disco en sí, finjas ser NTFS y alteres el disco clusters asignados directamente a un FCB en particular. Pero hacerlo es valiente. NTFS apenas está documentado, es complejo, sujeto a cambios y difícil de modificar incluso cuando no está montado por el sistema operativo, no importa cuando lo esté.

Así que la respuesta, me temo es no. No es posible a través de los mecanismos normales de seguridad de Windows agregar bytes adicionales a la mitad de un archivo como una inserción en lugar de como una operación de sobrescritura.

En su lugar, considere mirar su problema para ver si es apropiado para que pueda dividir sus archivos en archivos más pequeños y tener un archivo de índice. De esta manera podrás modificar el archivo de índice para insertar trozos adicionales. Al romper su dependencia de los datos que necesitan residir en un archivo, le resultará más fácil evitar el requisito del sistema de archivos de que un archivo sea una colección lógicamente contigua de bytes. A continuación, podrá modificar el archivo de índice para agregar trozos adicionales a su "pseduofile" sin necesidad de leer todo el pseudofile en la memoria.

 2
Author: SecurityMatt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-14 20:44:39

No necesita (y probablemente no pueda) modificar la tabla de acceso a archivos. Puede lograr lo mismo usando un controlador de filtro o un FS apilable. Consideremos un tamaño de racimo de 4K. Simplemente estoy escribiendo el diseño por las razones que explico al final.

  1. Creación de un nuevo archivo será un layout-map del archivo en un encabezado. El encabezado mencionará el número de entradas y una lista de entradas. El tamaño del encabezado será el mismo que el tamaño del clúster. Para la simplicidad que el cabecera de tamaño fijo con entradas 4K. Por ejemplo, supongamos que hay un archivo de decir 20KB el encabezado puede mencionar: [DWORD:5][DWORD:1][DWORD:2][DWORD:3][DWORD:4][DWORD:5]. Este archivo actualmente no ha tenido inserciones.

  2. Supongamos que alguien inserta un clúster después del sector 3. Usted puede agregar al final del archivo y cambiar el esquema de mapa: [5][1][2][3][5][6][4]

  3. Supongamos que alguien necesita buscar el grupo 4. Tendrá que acceder al layout-map y calcular el desplazamiento y luego buscar a ella. Será después de los primeros 5 grupos, así que comenzará en 16K.

  4. Supongamos que alguien lee o escribe en serie en el archivo. Las lecturas y escrituras tendrán que mapearse de la misma manera.

  5. Supongamos que el encabezado solo tiene una entrada más: necesitaremos extenderlo teniendo un puntero a un nuevo clúster al final del archivo utilizando el mismo formato que los otros punteros anteriores. Para saber que tenemos más de un grupo todo lo que necesitamos hacer es mirar el número de elementos y calcular el número de clústeres que se necesitan para almacenarlo.

Puede implementar todo lo anterior usando un controlador de filtro en Windows o un sistema de archivos apilable (LKM) en Linux. La implementación del nivel básico de funcionalidad está en el nivel de un mini proyecto de posgrado en dificultad. Hacer que esto funcione como un sistema de archivos comercial puede ser bastante difícil, especialmente porque no desea afectar las velocidades de E / S.

Tenga en cuenta que el filtro anterior no será afectado por cualquier cambio en el diseño del disco / desfragmentación, etc. También puede desfragmentar su propio archivo si cree que será útil.

 1
Author: user1952500,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-14 01:48:17

¿Entiendes que es casi 99.99% imposible insertar datos no alineados en lugares no alineados? (Tal vez algún truco basado en la compresión se puede utilizar. Creo que sí.

La solución "más fácil" es crear los registros de ejecución dispersos y luego escribir sobre los rangos dispersos.

  1. Haga algo con la caché NTFS. Lo mejor es realizar las operaciones en la unidad sin conexión/sin montar.
  2. Obtenga el registro del archivo(la respuesta de @ JerKimball suena útil, pero se queda corta se). Puede haber problemas si el archivo está desbordado con atributos y se almacenan.
  3. Accede a la lista de ejecución de datos del archivo. El concepto de ejecución de datos y el formato se describen aquí ( http://inform.pucp.edu.pe / ~inf232/Ntfs/ntfs_doc_v0.5/concepts/data_runs.html ) y algunos otros datos de formato NTFS se pueden ver en las páginas adyacentes.
  4. Itere a través de las ejecuciones de datos, acumulando la longitud del archivo, para encontrar el lugar de inserción correcto.
  5. Lo más probable es que encuentre que su punto de inserción está en el medio de la carrera. Tendrás que dividir la carrera que no es difícil. (Solo guarde las dos carreras resultantes por ahora.)
  6. Crear un registro de ejecución dispersa es muy fácil. Es solo la longitud de carrera (en clústeres) precedida por el byte, que contiene el tamaño del byte de la longitud en sus 4 bits más bajos (los 4 bits más altos deben ser cero para indicar una carrera de repuesto).
  7. Ahora necesita calcular cuántos bytes adicionales debe insertar en la lista de ejecuciones de datos, de alguna manera hacer camino para ellos y hacer la inserción / reemplazo.
  8. Entonces necesita arreglar el atributo file size para que sea consistente con las ejecuciones.
  9. Finalmente puede montar la unidad y escribir la información insertada sobre los puntos de repuesto.
 1
Author: Ark-kun,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-16 00:06:19

Todo depende realmente de cuál es el problema original, eso es lo que estás tratando de lograr. La modificación de una tabla FAT / NTFS no es el problema, es una solución a su problema potentially potencialmente elegante y eficiente, pero más probablemente altamente peligroso e inapropiado. Usted mencionó que no tiene control sobre los sistemas de los usuarios donde se utilizará, por lo que presumiblemente para al menos algunos de ellos el administrador se opondría a la piratería en el sistema de archivos interno.

De todos Modos, volvamos al problema. Dada la información incompleta, se pueden imaginar varios casos de uso, y la solución será fácil o difícil dependiendo del caso de uso.

  1. Si sabe que después de la edición el archivo no será necesario por algún tiempo, entonces guardar la edición en medio segundo es fácil close solo cierre la ventana y deje que la aplicación termine de guardar en segundo plano, incluso si toma media hora. Sé que esto suena tonto, pero esto es un caso de uso frecuente: una vez que termine de editar su archivo, lo guarda, cierra el programa y ya no necesita ese archivo durante mucho tiempo.

  2. A menos que lo hagas. Tal vez el usuario decide editar un poco más, o tal vez aparece otro usuario. En ambos casos, su aplicación puede detectar fácilmente que el archivo está en proceso de ser guardado en el disco duro (por ejemplo, puede tener alrededor de un archivo de guardia oculto mientras se guarda el archivo principal). En este caso, abriría un archivo tal cual (parcialmente guardado), pero presente al usuario la vista personalizada del archivo que lo hace parecer como si el archivo estuviera en el estado final. Después de todo, tiene toda la información sobre qué trozos de archivo deben moverse a dónde.

  3. A menos que el usuario necesite abrir el archivo inmediatamente en otro editor(este no es un caso muy común, especialmente para un formato de archivo muy especializado, pero entonces quién sabe). Si es así, ¿tiene acceso al código fuente de ese otro editor? O puedes hablar a los desarrolladores de ese otro editor y persuadirlos a tratar el archivo guardado incompletamente como si estuviera en el estado final (no es tan difícil all todo lo que se necesita es leer la información de desplazamiento del archivo guard). Me imagino que los desarrolladores de ese otro editor están igualmente frustrados con los largos tiempos de ahorro y con mucho gusto aceptarían su solución ya que ayudaría a su producto.

  4. ¿Qué más podríamos tener? Tal vez el usuario quiera copiar o mover el archivo de inmediato a algún lugar else. Microsoft probablemente no cambiará el Explorador de Windows para su beneficio. En ese caso, debería implementar el controlador UMDF o prohibir al usuario que lo haga (por ejemplo, cambiar el nombre del archivo original y ocultarlo, dejando un marcador de posición en blanco en su lugar; cuando el usuario intente copiar el archivo, al menos sabrá que algo salió mal).

  5. Otra posibilidad, que no encaja bien en la jerarquía anterior 1-4, surge si sabes de antemano qué archivos será editado. En ese caso, puede "pre-dispersar" el archivo insertando espacios aleatorios uniformemente a lo largo del volumen del archivo. Esto se debe a la naturaleza especial de su formato de archivo que mencionó: podría haber lagunas de ningún dato, siempre que los enlaces apunten correctamente a los siguientes fragmentos de datos. Si sabe qué archivos se editarán (no es una suposición irrazonable? ¿cuántos archivos de 10 Gb se encuentran alrededor de su disco duro?) se "infla" el archivo antes de que el usuario comience a editarlo (por ejemplo, la noche antes), y luego simplemente mueva estos trozos más pequeños de datos cuando necesite insertar nuevos datos. Esto, por supuesto, también se basa en la suposición de que no tiene que insertar DEMASIADO.

En cualquier caso, siempre hay más de una respuesta dependiendo de lo que sus usuarios realmente quieren. Pero mi consejo viene de la perspectiva de un diseñador, no de un programador.

 1
Author: Pasha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-19 05:13:47

Editado - otro enfoque - ¿qué tal cambiar a Mac para esta tarea? Tienen capacidades de edición superiores, con capacidades de automatización!

Editado - las especificaciones originales sugerían que el archivo se estaba modificando mucho, en su lugar se modifica una vez. Sugerir como otros han señalado para hacer la operación en segundo plano: copiar a un nuevo archivo, eliminar el archivo antiguo, cambiar el nombre del nuevo archivo a un archivo antiguo.

Abandonaría este enfoque. Una base de datos es lo que estás buscando./ AÑO

 -1
Author: Yimin Rong,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-03-20 13:47:42