NTFS备用数据stream – .NET

我如何从.NET创build/删除/读取/写入/ NTFS备用数据stream?

如果没有本地.NET支持,我将使用哪个Win32 API? 另外,我将如何使用它们,因为我不认为这是logging的?

不在.NET中:

http://support.microsoft.com/kb/105763

#include <windows.h> #include <stdio.h> void main( ) { HANDLE hFile, hStream; DWORD dwRet; hFile = CreateFile( "testfile", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL ); if( hFile == INVALID_HANDLE_VALUE ) printf( "Cannot open testfile\n" ); else WriteFile( hFile, "This is testfile", 16, &dwRet, NULL ); hStream = CreateFile( "testfile:stream", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, 0, NULL ); if( hStream == INVALID_HANDLE_VALUE ) printf( "Cannot open testfile:stream\n" ); else WriteFile(hStream, "This is testfile:stream", 23, &dwRet, NULL); } 

这里是一个C#

 using System.Runtime.InteropServices; class Program { static void Main(string[] args) { var mainStream = NativeMethods.CreateFileW( "testfile", NativeConstants.GENERIC_WRITE, NativeConstants.FILE_SHARE_WRITE, IntPtr.Zero, NativeConstants.OPEN_ALWAYS, 0, IntPtr.Zero); var stream = NativeMethods.CreateFileW( "testfile:stream", NativeConstants.GENERIC_WRITE, NativeConstants.FILE_SHARE_WRITE, IntPtr.Zero, NativeConstants.OPEN_ALWAYS, 0, IntPtr.Zero); } } public partial class NativeMethods { /// Return Type: HANDLE->void* ///lpFileName: LPCWSTR->WCHAR* ///dwDesiredAccess: DWORD->unsigned int ///dwShareMode: DWORD->unsigned int ///lpSecurityAttributes: LPSECURITY_ATTRIBUTES->_SECURITY_ATTRIBUTES* ///dwCreationDisposition: DWORD->unsigned int ///dwFlagsAndAttributes: DWORD->unsigned int ///hTemplateFile: HANDLE->void* [DllImportAttribute("kernel32.dll", EntryPoint = "CreateFileW")] public static extern System.IntPtr CreateFileW( [InAttribute()] [MarshalAsAttribute(UnmanagedType.LPWStr)] string lpFileName, uint dwDesiredAccess, uint dwShareMode, [InAttribute()] System.IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes, [InAttribute()] System.IntPtr hTemplateFile ); } public partial class NativeConstants { /// GENERIC_WRITE -> (0x40000000L) public const int GENERIC_WRITE = 1073741824; /// FILE_SHARE_DELETE -> 0x00000004 public const int FILE_SHARE_DELETE = 4; /// FILE_SHARE_WRITE -> 0x00000002 public const int FILE_SHARE_WRITE = 2; /// FILE_SHARE_READ -> 0x00000001 public const int FILE_SHARE_READ = 1; /// OPEN_ALWAYS -> 4 public const int OPEN_ALWAYS = 4; } 

他们没有本地.NET支持。 您必须使用P / Invoke调用本机Win32方法。

要创build它们,请使用如filename.txt:streamname类的path调用CreateFile 。 如果使用返回SafeFileHandle的interop调用,则可以使用它构造一个FileStream,然后可以读取和写入。

要列出文件中存在的stream,请使用FindFirstStreamW和FindNextStreamW (仅在Server 2003及更高版本上存在 – 不是XP)。

我不相信你可以删除一个stream,除了复制文件的其余部分,并离开其中一个stream。 设置长度为0也可以,但我没有尝试过。

您也可以在目录上备用数据stream。 您可以像访问文件一样访问它们 – C:\some\directory:streamname

stream可以在其上设置压缩,encryption和稀疏性,而不依赖于默认stream。

这个nuget软件包CodeFluent运行时客户端 (除其他实用程序之外)还有一个支持创build/读取/更新/删除/枚举操作的NtfsAlternateStream类 。

A首先,Microsoft®.NET Framework没有提供此function。 如果你想要它,简单而简单,你需要做一些互操作,直接或使用第三方库。

如果您使用的是Windows Server™2003或更高版本,则Kernel32.dll会向对应方提供FindFirstFile和FindNextFile,以提供您正在查找的确切function。 FindFirstStreamW和FindNextStreamW允许您查找并枚举特定文件中的所有备用数据stream,检索有关每个备用数据stream的信息,包括其名称和长度。 从托pipe代码中使用这些函数的代码非常类似于我在12月份的专栏中显示的代码,如图1所示。

图1使用FindFirstStreamW和FindNextStreamW

 [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode = true)] public sealed class SafeFindHandle : SafeHandleZeroOrMinusOneIsInvalid { private SafeFindHandle() : base(true) { } protected override bool ReleaseHandle() { return FindClose(this.handle); } [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] private static extern bool FindClose(IntPtr handle); } public class FileStreamSearcher { private const int ERROR_HANDLE_EOF = 38; private enum StreamInfoLevels { FindStreamInfoStandard = 0 } [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] private static extern SafeFindHandle FindFirstStreamW(string lpFileName, StreamInfoLevels InfoLevel, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData, uint dwFlags); [DllImport("kernel32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool FindNextStreamW(SafeFindHandle hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_STREAM_DATA lpFindStreamData); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private class WIN32_FIND_STREAM_DATA { public long StreamSize; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 296)] public string cStreamName; } public static IEnumerable<string> GetStreams(FileInfo file) { if (file == null) throw new ArgumentNullException("file"); WIN32_FIND_STREAM_DATA findStreamData = new WIN32_FIND_STREAM_DATA(); SafeFindHandle handle = FindFirstStreamW(file.FullName, StreamInfoLevels.FindStreamInfoStandard, findStreamData, 0); if (handle.IsInvalid) throw new Win32Exception(); try { do { yield return findStreamData.cStreamName; } while (FindNextStreamW(handle, findStreamData)); int lastError = Marshal.GetLastWin32Error(); if (lastError != ERROR_HANDLE_EOF) throw new Win32Exception(lastError); } finally { handle.Dispose(); } } } 

您只需调用FindFirstStreamW,传递给目标文件的完整path。 FindFirstStreamW的第二个参数决定了返回数据中所需的细节级别; 目前,只有一个级别(FindStreamInfoStandard),其数值为0.函数的第三个参数是一个指向WIN32_FIND_STREAM_DATA结构的指针(从技术上说,第三个参数指向的是第二个参数的值详细的信息级别,但目前只有一个级别,所有意图和目的这是一个WIN32_FIND_STREAM_DATA)。 我已经把这个结构的被pipe对象声明为一个类,并且在互操作签名中,我将它标记为一个指向结构的指针。 最后一个参数保留供将来使用,应为0.如果从FindFirstStreamW返回有效的句柄,则WIN32_FIND_STREAM_DATA实例包含有关find的stream的信息,并且可以将其cStreamName值作为第一个可用的stream名称返回给调用方。 FindNextStreamW接受从FindFirstStreamW返回的句柄,并填充提供的WIN32_FIND_STREAM_DATA关于下一个可用stream(如果存在)的信息。 如果另一个stream可用,则FindNextStreamW返回true,否则返回false。 因此,我不断地调用FindNextStreamW并产生结果stream名称,直到FindNextStreamW返回false。 发生这种情况时,我仔细检查最后一个错误值,以确保迭代停止,因为FindNextStreamW耗尽了stream,而不是出于某种意外的原因。 不幸的是,如果您使用的是Windows®XP或Windows 2000 Server,那么这些function对您而言是不可用的,但也有一些select。 第一个解决scheme涉及一个当前从Kernel32.dll,NTQueryInformationFile导出的未公开的函数。 然而,无证的function由于某种原因没有logging,将来可以随时更改甚至删除。 最好不要使用它们。 如果你想使用这个function,search网页,你会发现很多的参考和示例源代码。 但是要自己承担风险。 另一个解决scheme,以及我在图2中演示的一个解决scheme,依赖于从Kernel32.dll导出的两个函数,并且这些都是logging的。 顾名思义,BackupRead和BackupSeek是用于备份支持的Win32®API的一部分:

 BOOL BackupRead(HANDLE hFile, LPBYTE lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, BOOL bAbort, BOOL bProcessSecurity, LPVOID* lpContext); BOOL BackupSeek(HANDLE hFile, DWORD dwLowBytesToSeek, DWORD dwHighBytesToSeek, LPDWORD lpdwLowByteSeeked, LPDWORD lpdwHighByteSeeked, LPVOID* lpContext); 

图2使用BackupRead和BackupSeek

 public enum StreamType { Data = 1, ExternalData = 2, SecurityData = 3, AlternateData = 4, Link = 5, PropertyData = 6, ObjectID = 7, ReparseData = 8, SparseDock = 9 } public struct StreamInfo { public StreamInfo(string name, StreamType type, long size) { Name = name; Type = type; Size = size; } readonly string Name; public readonly StreamType Type; public readonly long Size; } public class FileStreamSearcher { [DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool BackupRead(SafeFileHandle hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [MarshalAs(UnmanagedType.Bool)] bool bAbort, [MarshalAs(UnmanagedType.Bool)] bool bProcessSecurity, ref IntPtr lpContext);[DllImport("kernel32.dll")] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool BackupSeek(SafeFileHandle hFile, uint dwLowBytesToSeek, uint dwHighBytesToSeek, out uint lpdwLowByteSeeked, out uint lpdwHighByteSeeked, ref IntPtr lpContext); public static IEnumerable<StreamInfo> GetStreams(FileInfo file) { const int bufferSize = 4096; using (FileStream fs = file.OpenRead()) { IntPtr context = IntPtr.Zero; IntPtr buffer = Marshal.AllocHGlobal(bufferSize); try { while (true) { uint numRead; if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Marshal.SizeOf(typeof(Win32StreamID)), out numRead, false, true, ref context)) throw new Win32Exception(); if (numRead > 0) { Win32StreamID streamID = (Win32StreamID)Marshal.PtrToStructure(buffer, typeof(Win32StreamID)); string name = null; if (streamID.dwStreamNameSize > 0) { if (!BackupRead(fs.SafeFileHandle, buffer, (uint)Math.Min(bufferSize, streamID.dwStreamNameSize), out numRead, false, true, ref context)) throw new Win32Exception(); name = Marshal.PtrToStringUni(buffer, (int)numRead / 2); } yield return new StreamInfo(name, streamID.dwStreamId, streamID.Size); if (streamID.Size > 0) { uint lo, hi; BackupSeek(fs.SafeFileHandle, uint.MaxValue, int.MaxValue, out lo, out hi, ref context); } } else break; } } finally { Marshal.FreeHGlobal(buffer); uint numRead; if (!BackupRead(fs.SafeFileHandle, IntPtr.Zero, 0, out numRead, true, false, ref context)) throw new Win32Exception(); } } } } 

BackupRead背后的想法是,它可以用来将文件中的数据读入缓冲区,然后写入备份存储介质。 但是,BackupRead对于查找组成目标文件的每个备用数据stream的信息也非常方便。 它将文件中的所有数据作为一系列离散字节stream(每个备用数据stream是这些字节stream之一)进行处理,并且每个数据stream前面都有一个WIN32_STREAM_ID结构。 因此,为了枚举所有的stream,你只需要从每个stream的开始读取所有这些WIN32_STREAM_ID结构(这是BackupSeek变得非常方便的地方,因为它可以用于从stream跳到stream而不用读取文件中的所有数据)。 首先,您需要为非托pipeWIN32_STREAM_ID结构创build一个受pipe对象:

 typedef struct _WIN32_STREAM_ID { DWORD dwStreamId; DWORD dwStreamAttributes; LARGE_INTEGER Size; DWORD dwStreamNameSize; WCHAR cStreamName[ANYSIZE_ARRAY]; } WIN32_STREAM_ID; 

大部分情况下,这就像你通过P / Invoke发送的任何其他结构一样。 但是,有一些并发症。 首先,WIN32_STREAM_ID是一个可变大小的结构。 它的最后一个成员cStreamName是一个长度为ANYSIZE_ARRAY的数组。 虽然ANYSIZE_ARRAY被定义为1,但cStreamName只是前四个字段之后的结构中其余数据的地址,这意味着如果结构被分配为大于sizeof(WIN32_STREAM_ID)字节,那么额外的空间将会实际上是cStreamName数组的一部分。 前一个字段dwStreamNameSize指定数组的长度。 虽然这对于Win32开发非常有用,但是对于需要将这些数据从非托pipe内存复制到托pipe内存的编组器来说,这对于BackupRead的互操作调用是非常重要的。 考虑到它的大小可变,封送器如何知道WIN32_STREAM_ID结构实际上有多大? 它不。 第二个问题与包装和alignment有关。 忽略cStreamName一段时间,请考虑以下可能性为您的托pipe的WIN32_STREAM_ID副本:

 [StructLayout(LayoutKind.Sequential)] public struct Win32StreamID { public int dwStreamId; public int dwStreamAttributes; public long Size; public int dwStreamNameSize; } 

Int32的大小是4个字节,而Int64的大小是8个字节。 因此,你会期望这个结构是20个字节。 但是,如果运行以下代码,则会发现两个值都是24,而不是20:

 int size1 = Marshal.SizeOf(typeof(Win32StreamID)); int size2 = sizeof(Win32StreamID); // in an unsafe context 

问题是编译器想要确保这些结构中的值始终在正确的边界上alignment。 四字节的值应该是可以被4整除的地址,8字节的值应该是可以被8整除的边界,依此类推。 现在想象一下,如果你要创build一个Win32StreamID结构数组,会发生什么。 数组第一个实例中的所有字段将被正确alignment。 例如,由于“大小”字段遵循两个32位整数,所以从数组起始处将是8个字节,对于一个8字节的值来说是完美的。 但是,如果结构的大小为20个字节,则数组中的第二个实例不会将其所有成员都正确alignment。 整数值都可以,但是long值是从数组开始的28个字节,这个值不能被8整除。为了解决这个问题,编译器将结构填充到24的大小,这样所有的该字段将始终正确alignment(假设数组本身是)。 如果编译器做正确的事情,你可能想知道为什么我担心这个。 你会看到为什么看图2中的代码。为了解决我描述的第一个封送问题,我实际上把cStreamName从Win32StreamID结构中排除了。 我使用BackupRead读取足够的字节来填充我的Win32StreamID结构,然后检查结构的dwStreamNameSize字段。 现在我知道这个名字有多长,我可以再次使用BackupRead从文件中读取string的值。 这一切都很好,但如果Marshal.SizeOf返回24我的Win32StreamID结构而不是20,我会试图读取太多的数据。 为了避免这种情况,我需要确保Win32StreamID的大小实际上是20而不是24.这可以用装饰结构的StructLayoutAttribute上的字段以两种不同的方式完成。 首先是使用Size字段,这个字段决定了运行时结构应该有多大:

 [StructLayout(LayoutKind.Sequential, Size = 20)] 

第二个选项是使用Pack字段。 Pack指示指定LayoutKind.Sequential值时应使用的包装大小,并控制结构内字段的alignment方式。 托pipe结构的默认包装大小是8.如果我将其更改为4,我得到我正在寻找的20字节结构(因为我实际上没有在数组中使用它,我不会失去效率或者由于这种包装变化可能导致的稳定性):

 [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct Win32StreamID { public StreamType dwStreamId; public int dwStreamAttributes; public long Size; public int dwStreamNameSize; // WCHAR cStreamName[1]; } 

有了这个代码,我现在可以枚举文件中的所有stream,如下所示:

 static void Main(string[] args) { foreach (string path in args) { Console.WriteLine(path + ":"); foreach (StreamInfo stream in FileStreamSearcher.GetStreams(new FileInfo(path))) { Console.WriteLine("\t{0}\t{1}\t{2}", stream.Name != null ? stream.Name : "(unnamed)", stream.Type, stream.Size); } } } 

你会注意到这个版本的FileStreamSearcher返回比使用FindFirstStreamW和FindNextStreamW的版本更多的信息。 BackupRead可以提供不仅仅是主stream和备用数据stream的数据,还可以在包含安全信息,重分析数据等的数据stream上运行。 如果您只想查看备用数据stream,则可以基于StreamInfo的Type属性进行过滤,这将是用于备用数据stream的StreamType.AlternateData。 要testing此代码,可以在命令提示符处使用echo命令创build具有替代数据stream的文件:

 > echo ".NET Matters" > C:\test.txt > echo "MSDN Magazine" > C:\test.txt:magStream > StreamEnumerator.exe C:\test.txt test.txt: (unnamed) SecurityData 164 (unnamed) Data 17 :magStream:$DATA AlternateData 18 > type C:\test.txt ".NET Matters" > more < C:\test.txt:magStream "MSDN Magazine" 

因此,现在您可以检索存储在文件中的所有备用数据stream的名称。 大。 但是如果你想实际操纵那些stream中的数据呢? 不幸的是,如果您尝试将一个备用数据stream的path传递给FileStream构造函数之一,将引发NotSupportedException:“给定path的格式不受支持”。 为了解决这个问题,可以通过直接访问从kernel32.dll公开的CreateFile函数来绕过FileStream的path规范化检查( 见图3 )。 我已经使用CreateFile函数的P / Invoke打开并检索指定path的SafeFileHandle,而不对path执行任何托pipe权限检查,因此可以包含备用数据stream标识符。 这个SafeFileHandle然后被用来创build一个新的托pipeFileStream,提供所需的访问。 有了这个,使用System.IO命名空间的function很容易操作备用数据stream的内容。 以下示例读取并打印出上例中创build的C:\ test.txt:magStream的内容:

 string path = @"C:\test.txt:magStream"; using (StreamReader reader = new StreamReader(CreateFileStream(path, FileAccess.Read, FileMode.Open, FileShare.Read))) { Console.WriteLine(reader.ReadToEnd()); } 

图3对CreateFile使用P / Invoke

 private static FileStream CreateFileStream(string path, FileAccess access, FileMode mode, FileShare share) { if (mode == FileMode.Append) mode = FileMode.OpenOrCreate; SafeFileHandle handle = CreateFile(path, access, share, IntPtr.Zero, mode, 0, IntPtr.Zero); if (handle.IsInvalid) throw new IOException("Could not open file stream.", new Win32Exception()); return new FileStream(handle, access); } [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern SafeFileHandle CreateFile(string lpFileName, FileAccess dwDesiredAccess, FileShare dwShareMode, IntPtr lpSecurityAttributes, FileMode dwCreationDisposition, int dwFlagsAndAttributes, IntPtr hTemplateFile); 

Stephen Toub于2006年1月在MSDN杂志上发表 。