我需要一种将一些文件簇插入文件中间以插入一些数据的方法。
通常情况下,我会读取整个文件并再次将其写回更改,但文件大小为数 GB,读取文件并再次将其写回需要 30 分钟。
集群大小不会打扰我;我基本上可以在我插入的集群的末尾写出零,它仍然可以在这种文件格式下工作。
如何使用 Windows 文件 API(或其他机制)修改文件的文件分配表,在文件中间的指定点插入一个或多个未使用的集群?
我需要一种将一些文件簇插入文件中间以插入一些数据的方法。
通常情况下,我会读取整个文件并再次将其写回更改,但文件大小为数 GB,读取文件并再次将其写回需要 30 分钟。
集群大小不会打扰我;我基本上可以在我插入的集群的末尾写出零,它仍然可以在这种文件格式下工作。
如何使用 Windows 文件 API(或其他机制)修改文件的文件分配表,在文件中间的指定点插入一个或多个未使用的集群?
[编辑:]
废话 - 我要说“这不可行,至少不能通过 MFT 修改,没有很多痛苦”;首先,NTFS MFT 结构本身并不是 100%“开放”的,所以我开始深入研究逆向工程领域,它具有我没有心情处理的法律影响。此外,在 .NET 中执行此操作是一个非常繁琐的映射和编组结构的过程,基于大量的猜测(并且不要让我开始了解大多数 MFT 结构都以奇怪的方式压缩的事实)。短篇小说,虽然我确实学到了很多关于 NTFS 如何“工作”的知识,但我离这个问题的解决方案还差得远。
[/编辑]
呃……太多的编组废话……
这让我觉得“有趣”,因此我不得不在这个问题上四处寻找......它仍然是一个“正在进行的答案”,但想发布我所需要帮助其他人想出的东西。:)
另外,我有一个粗略的感觉,这在 FAT32 上会更容易,但考虑到我只有 NTFS 可以使用......
所以 -大量的 pinvoking 和编组,所以让我们从那里开始并向后工作:
有人可能会猜到,标准的 .NET File/IO api 在这里对您没有多大帮助 - 我们需要设备级访问:
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
static extern SafeFileHandle CreateFile(
string lpFileName,
[MarshalAs(UnmanagedType.U4)] FileAccess dwDesiredAccess,
[MarshalAs(UnmanagedType.U4)] FileShare dwShareMode,
IntPtr lpSecurityAttributes,
[MarshalAs(UnmanagedType.U4)] FileMode dwCreationDisposition,
[MarshalAs(UnmanagedType.U4)] FileAttributes dwFlagsAndAttributes,
IntPtr hTemplateFile);
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern bool ReadFile(
SafeFileHandle hFile, // handle to file
byte[] pBuffer, // data buffer, should be fixed
int NumberOfBytesToRead, // number of bytes to read
IntPtr pNumberOfBytesRead, // number of bytes read, provide NULL here
ref NativeOverlapped lpOverlapped // should be fixed, if not null
);
[DllImport("Kernel32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern bool SetFilePointerEx(
SafeFileHandle hFile,
long liDistanceToMove,
out long lpNewFilePointer,
SeekOrigin dwMoveMethod);
因此,我们将使用这些讨厌的 win32 野兽:
// To the metal, baby!
using (var fileHandle = NativeMethods.CreateFile(
// Magic "give me the device" syntax
@"\\.\c:",
// MUST explicitly provide both of these, not ReadWrite
FileAccess.Read | FileAccess.Write,
// MUST explicitly provide both of these, not ReadWrite
FileShare.Write | FileShare.Read,
IntPtr.Zero,
FileMode.Open,
FileAttributes.Normal,
IntPtr.Zero))
{
if (fileHandle.IsInvalid)
{
// Doh!
throw new Win32Exception();
}
else
{
// Boot sector ~ 512 bytes long
byte[] buffer = new byte[512];
NativeOverlapped overlapped = new NativeOverlapped();
NativeMethods.ReadFile(fileHandle, buffer, buffer.Length, IntPtr.Zero, ref overlapped);
// Pin it so we can transmogrify it into a FAT structure
var handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
try
{
// note, I've got an NTFS drive, change yours to suit
var bootSector = (BootSector_NTFS)Marshal.PtrToStructure(
handle.AddrOfPinnedObject(),
typeof(BootSector_NTFS));
哇,哇哇 - 到底是什么BootSector_NTFS
?它是一个字节映射struct
,尽可能接近 NTFS 结构的样子(包括 FAT32):
[StructLayout(LayoutKind.Sequential, CharSet=CharSet.Ansi, Pack=0)]
public struct JumpBoot
{
[MarshalAs(UnmanagedType.ByValArray, ArraySubType=UnmanagedType.U1, SizeConst=3)]
public byte[] BS_jmpBoot;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=8)]
public string BS_OEMName;
}
[StructLayout(LayoutKind.Explicit, CharSet = CharSet.Ansi, Pack = 0, Size = 90)]
public struct BootSector_NTFS
{
[FieldOffset(0)]
public JumpBoot JumpBoot;
[FieldOffset(0xb)]
public short BytesPerSector;
[FieldOffset(0xd)]
public byte SectorsPerCluster;
[FieldOffset(0xe)]
public short ReservedSectorCount;
[FieldOffset(0x10)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 5)]
public byte[] Reserved0_MUSTBEZEROs;
[FieldOffset(0x15)]
public byte BPB_Media;
[FieldOffset(0x16)]
public short Reserved1_MUSTBEZERO;
[FieldOffset(0x18)]
public short SectorsPerTrack;
[FieldOffset(0x1A)]
public short HeadCount;
[FieldOffset(0x1c)]
public int HiddenSectorCount;
[FieldOffset(0x20)]
public int LargeSectors;
[FieldOffset(0x24)]
public int Reserved6;
[FieldOffset(0x28)]
public long TotalSectors;
[FieldOffset(0x30)]
public long MftClusterNumber;
[FieldOffset(0x38)]
public long MftMirrorClusterNumber;
[FieldOffset(0x40)]
public byte ClustersPerMftRecord;
[FieldOffset(0x41)]
public byte Reserved7;
[FieldOffset(0x42)]
public short Reserved8;
[FieldOffset(0x44)]
public byte ClustersPerIndexBuffer;
[FieldOffset(0x45)]
public byte Reserved9;
[FieldOffset(0x46)]
public short ReservedA;
[FieldOffset(0x48)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]
public byte[] SerialNumber;
[FieldOffset(0x50)]
public int Checksum;
[FieldOffset(0x54)]
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 0x1AA)]
public byte[] BootupCode;
[FieldOffset(0x1FE)]
public ushort EndOfSectorMarker;
public long GetMftAbsoluteIndex(int recordIndex = 0)
{
return (BytesPerSector * SectorsPerCluster * MftClusterNumber) + (GetMftEntrySize() * recordIndex);
}
public long GetMftEntrySize()
{
return (BytesPerSector * SectorsPerCluster * ClustersPerMftRecord);
}
}
// Note: dont have fat32, so can't verify all these...they *should* work, tho
// refs:
// http://www.pjrc.com/tech/8051/ide/fat32.html
// http://msdn.microsoft.com/en-US/windows/hardware/gg463084
[StructLayout(LayoutKind.Explicit, CharSet=CharSet.Auto, Pack=0, Size=90)]
public struct BootSector_FAT32
{
[FieldOffset(0)]
public JumpBoot JumpBoot;
[FieldOffset(11)]
public short BPB_BytsPerSec;
[FieldOffset(13)]
public byte BPB_SecPerClus;
[FieldOffset(14)]
public short BPB_RsvdSecCnt;
[FieldOffset(16)]
public byte BPB_NumFATs;
[FieldOffset(17)]
public short BPB_RootEntCnt;
[FieldOffset(19)]
public short BPB_TotSec16;
[FieldOffset(21)]
public byte BPB_Media;
[FieldOffset(22)]
public short BPB_FATSz16;
[FieldOffset(24)]
public short BPB_SecPerTrk;
[FieldOffset(26)]
public short BPB_NumHeads;
[FieldOffset(28)]
public int BPB_HiddSec;
[FieldOffset(32)]
public int BPB_TotSec32;
[FieldOffset(36)]
public FAT32 FAT;
}
[StructLayout(LayoutKind.Sequential)]
public struct FAT32
{
public int BPB_FATSz32;
public short BPB_ExtFlags;
public short BPB_FSVer;
public int BPB_RootClus;
public short BPB_FSInfo;
public short BPB_BkBootSec;
[MarshalAs(UnmanagedType.ByValArray, SizeConst=12)]
public byte[] BPB_Reserved;
public byte BS_DrvNum;
public byte BS_Reserved1;
public byte BS_BootSig;
public int BS_VolID;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=11)]
public string BS_VolLab;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst=8)]
public string BS_FilSysType;
}
所以现在我们可以将整个混乱'o'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();
}
啊,更多的原生结构要讨论 - 所以 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);
}
}
罗伯特,我不认为如果不主动操作文件系统的文件系统数据结构,从它的声音来看,它是挂载的,你想要实现的目标是不可能的。我想我不必告诉你这种做法有多危险和不明智。
但如果你需要这样做,我想我可以给你一张“餐巾纸背面的草图”让你开始:
您可以利用 NTFS 的“稀疏文件”支持通过调整 LCN/VCN 映射来简单地添加“间隙”。完成后,只需打开文件,寻找新位置并写入数据。NTFS 将透明地分配空间并将数据写入文件中间,您在其中创建了一个洞。
有关更多信息,请查看有关NTFS 中的碎片整理支持的页面,以获取有关如何稍微操作事物并允许您在文件中间插入集群的提示。至少通过对此类事情使用认可的 API,您不太可能将文件系统损坏到无法修复的程度,尽管我猜您仍然可以可怕地冲洗您的文件。
获取所需文件的检索指针,将它们拆分到需要的位置,根据需要添加尽可能多的额外空间,然后移动文件。Russinovich/Ionescu “Windows Internals”一书中有一个关于这类事情的有趣章节(http://www.amazon.com/Windows%C2%AE-Internals-Including-Windows-Developer/dp/0735625301)
抽象问题,抽象答案:
当然可以在 FAT 中执行此操作,并且可能在大多数其他 FS 中,您实际上是在对文件进行分段,而不是更常见的碎片整理过程。
FAT 是用簇指针组织的,这些指针产生一个簇号链,其中存储数据,第一个链接索引与文件记录一起存储,第二个链接索引存储在索引[第一个链接的编号]处的分配表中,等等。只要您插入的数据在集群的边界处结束,就可以在链中的任何位置插入另一个链接。
通过找到一个开源库,您可能会更轻松地在 C 中执行此操作。虽然在 C# 中使用 PInvoke 可能可以做到这一点,但您不会发现任何好的示例代码可供您开始使用。
我怀疑您对文件格式(视频文件?)没有任何控制权,如果您这样做,那么首先设计数据存储以避免问题会容易得多。
您不需要(也可能不能)修改文件访问表。您可以使用过滤器驱动程序或可堆叠的 FS 来实现相同的目的。让我们考虑一个 4K 的集群大小。我只是出于我在最后解释的原因而写出设计。
新文件的创建将在标题中显示文件的布局图。标题将提及条目数和条目列表。标头的大小将与集群的大小相同。为简单起见,让标题为固定大小的 4K 条目。例如,假设有一个 20KB 的文件,标题可能会提到:[DWORD:5][DWORD:1][DWORD:2][DWORD:3][DWORD:4][DWORD:5]。该文件当前没有插入。
假设有人在扇区 3 之后插入了一个簇。您可以将其添加到文件末尾并将 layout-map 更改为:[5][1][2][3][5][6][4]
假设有人需要寻找聚类 4。您将需要访问布局图并计算偏移量,然后寻找它。它将在前 5 个集群之后,因此将从 16K 开始。
假设有人连续读取或写入文件。读取和写入必须以相同的方式映射。
假设标题只剩下一个条目:我们需要通过在文件末尾使用与上面其他指针相同的格式指向新簇的指针来扩展它。要知道我们有多个集群,我们需要做的就是查看项目的数量并计算存储它所需的集群数量。
您可以使用 Windows 上的过滤器驱动程序或 Linux 上的可堆叠文件系统 (LKM) 来实现上述所有功能。实现基本级别的功能是在困难的研究生学校迷你项目的水平上。让它作为商业文件系统工作可能非常具有挑战性,尤其是因为您不想影响 IO 速度。
请注意,上述过滤器不会受到磁盘布局/碎片整理等任何更改的影响。如果您认为这会有所帮助,您也可以对自己的文件进行碎片整理。
不,您要问的内容在 Windows 中无法直接实现。
这是因为在 Windows 中,文件是逻辑上连续的字节集合,不可能在不覆盖的情况下将字节插入文件中间。
为了理解为什么,让我们进行一个思想实验,看看如果可能的话,这意味着什么。
首先,内存映射文件会突然变得更加复杂。如果我们将文件映射到特定地址,然后在其中添加一些额外的字节,这对内存映射意味着什么?现在内存映射是否应该突然移动?如果是这样,不希望它发生的程序会发生什么?
其次,让我们考虑如果两个句柄对同一个文件打开,并且一个在该文件的中间插入额外的字节,GetFilePointer 会发生什么。假设进程 A 已打开文件以供读取,而进程 B 已打开文件以供读取和写入。
进程 A 想在进行几次读取时保存它的位置,所以它写了一些代码有点像
DWORD DoAndThenRewind(HANDLE hFile, FARPROC fp){
DWORD result;
LARGEINTEGER zero = { 0 };
LARGEINTEGER li;
SetFilePointer(hFile, zero, &li, FILE_CURRENT);
result = fp();
SetFilePointer(hFile, &li, &li, FILE_BEGIN);
return result;
}
现在如果进程 B 想在文件中插入一些额外的字节,这个函数会发生什么?好吧,如果我们在进程 A 当前所在的位置之后添加字节,一切都很好——文件指针(即从文件开头开始的线性地址)在前后保持不变,一切都很好。
但是,如果我们在进程 A 所在的位置之前添加额外的字节,那么,突然我们捕获的文件指针都未对齐,并且开始发生坏事。
或者换一种说法,在文件中间添加字节意味着我们突然需要发明更聪明的方法来描述我们在文件中的位置以用于倒带,因为文件不再是逻辑上连续的字节选择。
因此,到目前为止,我们已经讨论了为什么 Windows 公开这种功能可能是个坏主意;但这并不能真正回答“它实际上是否可能”的问题。这里的答案仍然是否定的。这不可能。
为什么?因为没有这样的功能暴露给用户模式程序来执行此操作。作为一个用户模式程序,你有一个获取文件句柄的机制(NtCreateFile/NtOpenFile),你可以通过 NtReadFile/NtWriteFile 读取和写入它,你可以通过 NtSetFileInformation 查找并重命名和删除它,然后你可以通过 NtClose 释放句柄引用。
即使在内核模式下,您也没有更多选择。文件系统 API 是从您那里抽象出来的,文件系统将文件视为逻辑上连续的字节集合,而不是字节范围的链接列表或任何可以轻松公开一种方法以便您在中间插入非覆盖字节的方法一份文件。
这并不是说它本身是不可能的。正如其他人所提到的,您可以打开磁盘本身,伪装成 NTFS 并直接更改分配给特定 FCB 的磁盘集群。但这样做是勇敢的。NTFS 几乎没有文档记录,很复杂,可能会发生变化,并且即使它没有被操作系统挂载也很难修改,不管它什么时候挂载。
所以,恐怕答案是否定的。不可能通过正常的安全 Windows 机制在文件中间添加额外字节作为插入而不是覆盖操作。
相反,请考虑查看您的问题,看看是否适合将文件分块为较小的文件并拥有一个索引文件。这样您就可以修改索引文件以插入额外的块。通过打破对需要驻留在一个文件中的数据的依赖,您会发现更容易避免文件系统要求文件是逻辑上连续的字节集合。然后,您将能够修改索引文件以向“pseduofile”添加额外的块,而无需将整个伪文件读入内存。
您是否了解在未对齐的位置插入未对齐的数据几乎 99.99% 是不可能的?(也许可以使用一些基于压缩的hack。)我认为你可以。
“最简单”的解决方案是创建稀疏运行记录,然后覆盖稀疏范围。
这完全取决于原始问题是什么,这就是您要实现的目标。修改 FAT / NTFS 表不是问题,它是您问题的解决方案 - 可能是优雅和高效的,但更可能是非常危险和不合适的。您提到您无法控制将使用它的用户系统,因此可能至少对于其中一些用户,管理员会反对入侵文件系统内部。
无论如何,让我们回到问题上来。鉴于信息不完整,可以想象几个用例,根据用例,解决方案将是简单的还是困难的。
如果您知道在编辑后一段时间内不需要文件,那么在半秒内保存编辑很容易 - 只需关闭窗口并让应用程序在后台完成保存,即使需要半秒小时。我知道这听起来很愚蠢,但这是一个常见的用例——一旦你完成了文件的编辑,你保存它,关闭程序,很长一段时间你都不需要那个文件了。
除非你这样做。也许用户决定再编辑一些,或者另一个用户出现。在这两种情况下,您的应用程序都可以轻松地检测到文件正在保存到硬盘的过程中(例如,在保存主文件时,您可能有一个隐藏的保护文件)。在这种情况下,您将按原样(部分保存)打开文件,但向用户显示文件的自定义视图,使其看起来好像文件处于最终状态。毕竟,您拥有关于哪些文件块必须移动到哪里的所有信息。
除非用户需要立即在另一个编辑器中打开文件(这不是很常见的情况,尤其是对于非常专业的文件格式,但谁知道呢)。如果是这样,您是否有权访问该其他编辑器的源代码?或者您能否与其他编辑器的开发人员交谈并说服他们将未完全保存的文件视为处于最终状态(这并不难——只需从保护文件中读取偏移信息)。我可以想象,另一个编辑器的开发人员同样对保存时间过长感到沮丧,并且很乐意接受您的解决方案,因为这将有助于他们的产品。
我们还能有什么?也许用户想立即将文件复制或移动到其他地方。Microsoft 可能不会为了您的利益而更改 Windows Explorer。在这种情况下,您要么需要实现 UMDF 驱动程序,要么明确禁止用户这样做(例如,重命名原始文件并将其隐藏,在其位置留下空白占位符;当用户至少尝试复制文件时他会知道出了什么问题)。
如果您事先知道将编辑哪些文件,则会出现另一种可能性,它不能很好地适合上述层次结构 1-4 。在这种情况下,您可以“预稀疏”文件,沿文件的体积均匀地插入随机间隙。这是由于您提到的文件格式的特殊性质:如果链接正确指向下一个数据块,则可能没有数据间隙。如果你知道哪些文件将被编辑(不是不合理的假设——你的硬盘驱动器上有多少 10Gb 文件?)你在用户开始编辑之前“膨胀”文件(比如前一天晚上),然后四处走动当您需要插入新数据时,这些较小的数据块。这当然也依赖于您不必插入太多的假设。
无论如何,根据您的用户实际想要什么,总是有不止一个答案。但我的建议来自设计师的观点,而不是程序员的观点。
还有一种可能。
创建一个用户模式文件系统,例如使用 FUSE 或 Dokan,并且您设计为保存单个文件。从那里,您可以使用任何您想要的解决方案,包括将多个文件的片段连接在一起,使某些东西看起来好像是一个大文件。
然后创建指向该文件的符号链接。
已编辑 - 另一种方法 - 为这项任务切换到 Mac 怎么样?他们具有出色的编辑功能,具有自动化功能!
已编辑 - 原始规格表明该文件被修改了很多,而是修改了一次。建议像其他人指出的那样在后台进行操作:复制到新文件,删除旧文件,将新文件重命名为旧文件。
我会放弃这种方法。数据库就是你要找的。/YR