C# NTFS 备用数据流 - .NET
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/604960/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
NTFS Alternate Data Streams - .NET
提问by user72491
How would I create/ delete/ read/ write/ NTFS alternate data streams from .NET?
我将如何从 .NET 创建/删除/读取/写入/NTFS 备用数据流?
If there is no native .NET support, which Win32 API's would I use? Also, how would I use them, as I don't think this is documented?
如果没有本机 .NET 支持,我会使用哪些 Win32 API?另外,我将如何使用它们,因为我认为这没有记录?
采纳答案by Otávio Décio
Not in .NET:
不在 .NET 中:
http://support.microsoft.com/kb/105763
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);
}
回答by JaredPar
Here is a version for C#
这是 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;
}
回答by Zack Elan
There is no native .NET support for them. You have to use P/Invoke to call the native Win32 methods.
没有对它们的原生 .NET 支持。您必须使用 P/Invoke 来调用本机 Win32 方法。
To create them, call CreateFilewith a path like filename.txt:streamname
. If you use the interop call that returns a SafeFileHandle, you can use that to construct a FileStream that you can then read & write to.
要创建它们,调用的CreateFile与像的路径filename.txt:streamname
。如果您使用返回 SafeFileHandle 的互操作调用,您可以使用它来构造一个 FileStream,然后您可以读取和写入。
To list the streams that exist on a file, use FindFirstStreamWand FindNextStreamW(which exist only on Server 2003 and later - not XP).
要列出文件中存在的流,请使用FindFirstStreamW和FindNextStreamW(仅在 Server 2003 及更高版本上存在 - 而不是 XP)。
I don't believe you can delete a stream, except by copying the rest of the file and leaving off one of the streams. Setting the length to 0 may also work, but I haven't tried it.
我不相信您可以删除流,除非复制文件的其余部分并保留其中一个流。将长度设置为 0 也可能有效,但我还没有尝试过。
You can also have alternate data streams on a directory. You access them the same as with files - C:\some\directory:streamname
.
您还可以在目录上拥有备用数据流。您可以像使用文件一样访问它们 - C:\some\directory:streamname
.
Streams can have compression, encryption and sparseness set on them independent of the default stream.
流可以具有独立于默认流的压缩、加密和稀疏设置。
回答by Václav Dajbych
A First, nothing in the Microsoft? .NET Framework provides this functionality. If you want it, plain and simple you'll need to do some sort of interop, either directly or using a third-party library.
A 第一,在微软什么都没有?.NET Framework 提供了此功能。如果您需要,简单明了,您需要直接或使用第三方库进行某种互操作。
If you're using Windows Server? 2003 or later, Kernel32.dll exposes counterparts to FindFirstFile and FindNextFile that provide the exact functionality you're looking for. FindFirstStreamW and FindNextStreamW allow you to find and enumerate all of the Alternate Data Streams within a particular file, retrieving information about each, including its name and its length. The code for using these functions from managed code is very similar to that which I showed in my December column, and is shown in Figure 1.
如果您使用的是 Windows Server?2003 或更高版本,Kernel32.dll 向 FindFirstFile 和 FindNextFile 公开提供您正在寻找的确切功能的对应项。FindFirstStreamW 和 FindNextStreamW 允许您查找和枚举特定文件中的所有备用数据流,检索有关每个备用数据流的信息,包括其名称和长度。从托管代码中使用这些函数的代码与我在 12 月专栏中展示的非常相似,如图 1 所示。
Figure?1?Using FindFirstStreamW and FindNextStreamW
图 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();
}
}
}
You simply call FindFirstStreamW, passing to it the full path to the target file. The second parameter to FindFirstStreamW dictates the level of detail you want in the returned data; currently, there is only one level (FindStreamInfoStandard), which has a numerical value of 0. The third parameter to the function is a pointer to a WIN32_FIND_STREAM_DATA structure (technically, what the third parameter points to is dictated by the value of the second parameter detailing the information level, but as there's currently only one level, for all intents and purposes this is a WIN32_FIND_STREAM_DATA). I've declared that structure's managed counterpart as a class, and in the interop signature I've marked it to be marshaled as a pointer to a struct. The last parameter is reserved for future use and should be 0. If a valid handle is returned from FindFirstStreamW, the WIN32_FIND_STREAM_DATA instance contains information about the stream found, and its cStreamName value can be yielded back to the caller as the first stream name available. FindNextStreamW accepts the handle returned from FindFirstStreamW and fills the supplied WIN32_FIND_STREAM_DATA with information about the next stream available, if it exists. FindNextStreamW returns true if another stream is available, or false if not. As a result, I continually call FindNextStreamW and yield the resulting stream name until FindNextStreamW returns false. When that happens, I double check the last error value to make sure that the iteration stopped because FindNextStreamW ran out of streams, and not for some unexpected reason. Unfortunately, if you're using Windows? XP or Windows 2000 Server, these functions aren't available to you, but there are a couple of alternatives. The first solution involves an undocumented function currently exported from Kernel32.dll, NTQueryInformationFile. However, undocumented functions are undocumented for a reason, and they can be changed or even removed at any time in the future. It's best not to use them. If you do want to use this function, search the Web and you'll find plenty of references and sample source code. But do so at your own risk. Another solution, and one which I've demonstrated in Figure 2, relies on two functions exported from Kernel32.dll, and these are documented. As their names imply, BackupRead and BackupSeek are part of the Win32? API for backup support:
您只需调用 FindFirstStreamW,将目标文件的完整路径传递给它。FindFirstStreamW 的第二个参数规定了您想要的返回数据的详细程度;目前,只有一层(FindStreamInfoStandard),其数值为0。该函数的第三个参数是一个指向WIN32_FIND_STREAM_DATA结构的指针(从技术上讲,第三个参数指向的是第二个参数的值详细说明信息级别,但由于目前只有一个级别,对于所有意图和目的,这是一个 WIN32_FIND_STREAM_DATA)。我已经将该结构的托管对应物声明为一个类,并且在互操作签名中我已将它标记为作为指向结构的指针进行封送处理。最后一个参数保留供将来使用,应为 0。如果从 FindFirstStreamW 返回有效句柄,则 WIN32_FIND_STREAM_DATA 实例包含有关找到的流的信息,并且其 cStreamName 值可以作为第一个可用的流名称返回给调用者。FindNextStreamW 接受从 FindFirstStreamW 返回的句柄,并用有关下一个可用流(如果存在)的信息填充提供的 WIN32_FIND_STREAM_DATA。如果另一个流可用,则 FindNextStreamW 返回 true,否则返回 false。因此,我不断调用 FindNextStreamW 并生成结果流名称,直到 FindNextStreamW 返回 false。发生这种情况时,我会仔细检查最后一个错误值,以确保迭代停止是因为 FindNextStreamW 用完了流,而不是出于某种意外原因。不幸的是,如果您使用的是 Windows?XP 或 Windows 2000 服务器,这些功能对您不可用,但有几种选择。第一个解决方案涉及当前从 Kernel32.dll NTQueryInformationFile 导出的未记录函数。但是,未记录的功能是有原因的,它们可以在将来的任何时间更改甚至删除。最好不要使用它们。如果您确实想使用此功能,请在网上搜索,您会找到大量参考资料和示例源代码。但这样做的风险由您自己承担。另一种解决方案,我已经在 并且可以在将来随时更改甚至删除它们。最好不要使用它们。如果您确实想使用此功能,请在网上搜索,您会找到大量参考资料和示例源代码。但这样做的风险由您自己承担。另一种解决方案,我已经在 并且可以在将来随时更改甚至删除它们。最好不要使用它们。如果您确实想使用此功能,请在网上搜索,您会找到大量参考资料和示例源代码。但这样做的风险由您自己承担。另一种解决方案,我已经在图 2依赖于从 Kernel32.dll 导出的两个函数,这些函数被记录在案。顾名思义,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);
Figure?2?Using BackupRead and BackupSeek
图 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();
}
}
}
}
The idea behind BackupRead is that it can be used to read data from a file into a buffer, which can then be written to the backup storage medium. However, BackupRead is also very handy for finding out information about each of the Alternate Data Streams that make up the target file. It processes all of the data in the file as a series of discrete byte streams (each Alternate Data Stream is one of these byte streams), and each of the streams is preceded by a WIN32_STREAM_ID structure. Thus, in order to enumerate all of the streams, you simply need to read through all of these WIN32_STREAM_ID structures from the beginning of each stream (this is where BackupSeek becomes very handy, as it can be used to jump from stream to stream without having to read through all of the data in the file). To begin, you first need to create a managed counterpart for the unmanaged WIN32_STREAM_ID structure:
BackupRead 背后的想法是它可以用于将数据从文件读取到缓冲区,然后可以将其写入备份存储介质。但是,BackupRead 对于查找有关构成目标文件的每个备用数据流的信息也非常方便。它将文件中的所有数据处理为一系列离散的字节流(每个备用数据流都是这些字节流中的一个),并且每个流前面都有一个 WIN32_STREAM_ID 结构。因此,为了枚举所有流,您只需要从每个流的开头读取所有这些 WIN32_STREAM_ID 结构(这是 BackupSeek 变得非常方便的地方,因为它可以用于从流跳转到流而无需读取文件中的所有数据)。开始,
typedef struct _WIN32_STREAM_ID {
DWORD dwStreamId; DWORD dwStreamAttributes;
LARGE_INTEGER Size;
DWORD dwStreamNameSize;
WCHAR cStreamName[ANYSIZE_ARRAY];
} WIN32_STREAM_ID;
For the most part, this is like any other structure you'd marshal through P/Invoke. However, there are a few complications. First and foremost, WIN32_STREAM_ID is a variable-sized structure. Its last member, cStreamName, is an array with length ANYSIZE_ARRAY. While ANYSIZE_ARRAY is defined to be 1, cStreamName is just the address of the rest of the data in the structure after the previous four fields, which means that if the structure is allocated to be larger than sizeof (WIN32_STREAM_ID) bytes, that extra space will in effect be part of the cStreamName array. The previous field, dwStreamNameSize, specifies exactly how long the array is. While this is great for Win32 development, it wreaks havoc on a marshaler that needs to copy this data from unmanaged memory to managed memory as part of the interop call to BackupRead. How does the marshaler know how big the WIN32_STREAM_ID structure actually is, given that it's variable sized? It doesn't. The second problem has to do with packing and alignment. Ignoring cStreamName for a moment, consider the following possibility for your managed WIN32_STREAM_ID counterpart:
在大多数情况下,这就像您通过 P/Invoke 编组的任何其他结构一样。但是,有一些并发症。首先,WIN32_STREAM_ID 是一个可变大小的结构。它的最后一个成员 cStreamName 是一个长度为 ANYSIZE_ARRAY 的数组。而 ANYSIZE_ARRAY 定义为 1,cStreamName 只是结构中前四个字段后其余数据的地址,这意味着如果分配结构大于 sizeof (WIN32_STREAM_ID) 个字节,那么额外的空间将实际上是 cStreamName 数组的一部分。前一个字段 dwStreamNameSize 指定数组的确切长度。虽然这对 Win32 开发非常有用,但它对需要将此数据从非托管内存复制到托管内存作为对 BackupRead 的互操作调用的一部分的封送拆收器造成了严重破坏。考虑到它的大小可变,封送拆收器如何知道 WIN32_STREAM_ID 结构实际上有多大?它没有。第二个问题与打包和对齐有关。暂时忽略 cStreamName,请考虑托管 WIN32_STREAM_ID 对应项的以下可能性:
[StructLayout(LayoutKind.Sequential)]
public struct Win32StreamID {
public int dwStreamId;
public int dwStreamAttributes;
public long Size;
public int dwStreamNameSize;
}
An Int32 is 4 bytes in size and an Int64 is 8 bytes. Thus, you would expect this struct to be 20 bytes. However, if you run the following code, you'll find that both values are 24, not 20:
Int32 的大小为 4 个字节,Int64 的大小为 8 个字节。因此,您希望此结构为 20 个字节。但是,如果您运行以下代码,您会发现两个值都是 24,而不是 20:
int size1 = Marshal.SizeOf(typeof(Win32StreamID));
int size2 = sizeof(Win32StreamID); // in an unsafe context
The issue is that the compiler wants to make sure that the values within these structures are always aligned on the proper boundary. Four-byte values should be at addresses divisible by 4, 8-byte values should be at boundaries divisible by 8, and so on. Now imagine what would happen if you were to create an array of Win32StreamID structures. All of the fields in the first instance of the array would be properly aligned. For example, since the Size field follows two 32-bit integers, it would be 8 bytes from the start of the array, perfect for an 8-byte value. However, if the structure were 20-bytes in size, the second instance in the array would not have all of its members properly aligned. The integer values would all be fine, but the long value would be 28 bytes from the start of the array, a value not evenly divisible by 8. To fix this, the compiler pads the structure to a size of 24, such that all of the fields will always be properly aligned (assuming the array itself is). If the compiler's doing the right thing, you might be wondering why I'm concerned about this. You'll see why if you look at the code in Figure 2. In order to get around the first marshaling issue I described, I do in fact leave the cStreamName out of the Win32StreamID structure. I use BackupRead to read in enough bytes to fill my Win32StreamID structure, and then I examine the structure's dwStreamNameSize field. Now that I know how long the name is, I can use BackupRead again to read in the string's value from the file. That's all well and dandy, but if Marshal.SizeOf returns 24 for my Win32StreamID structure instead of 20, I'll be attempting to read too much data. To avoid this, I need to make sure that the size of Win32StreamID is in fact 20 and not 24. This can be accomplished in two different ways using fields on the StructLayoutAttribute that adorns the structure. The first is to use the Size field, which dictates to the runtime exactly how big the structure should be:
问题是编译器希望确保这些结构中的值始终在正确的边界上对齐。四字节值应位于可被 4 整除的地址处,8 字节值应位于可被 8 整除的边界处,依此类推。现在想象一下,如果您要创建一个 Win32StreamID 结构数组会发生什么。数组第一个实例中的所有字段都将正确对齐。例如,由于 Size 字段跟在两个 32 位整数之后,所以它距离数组的开头有 8 个字节,非常适合 8 个字节的值。但是,如果结构的大小为 20 字节,则数组中的第二个实例不会正确对齐其所有成员。整数值都可以,但长值将是距数组开头的 28 个字节,该值不能被 8 整除。要解决这个问题,编译器将结构填充到 24 的大小,这样所有字段将始终正确对齐(假设数组本身是)。如果编译器在做正确的事情,您可能想知道为什么我会担心这一点。如果您查看图 2 中的代码,您就会明白为什么。为了解决我描述的第一个封送问题,我实际上将 cStreamName 排除在 Win32StreamID 结构之外。我使用 BackupRead 读取足够的字节来填充我的 Win32StreamID 结构,然后我检查该结构的 dwStreamNameSize 字段。现在我知道名称有多长,我可以再次使用 BackupRead 从文件中读取字符串的值。这一切都很好,但如果 Marshal.SizeOf 为我的 Win32StreamID 结构返回 24 而不是 20,我将尝试读取太多数据。为了避免这种情况,我需要确保 Win32StreamID 的大小实际上是 20 而不是 24。这可以通过两种不同的方式使用装饰结构的 StructLayoutAttribute 上的字段来完成。第一种是使用 Size 字段,它向运行时指示结构应该有多大:
[StructLayout(LayoutKind.Sequential, Size = 20)]
The second option is to use the Pack field. Pack indicates the packing size that should be used when the LayoutKind.Sequential value is specified and controls the alignment of the fields within the structure. The default packing size for a managed structure is 8. If I change that to 4, I get the 20-byte structure I'm looking for (and as I'm not actually using this in an array, I don't lose efficiency or stability that might result from such a packing change):
第二个选项是使用 Pack 字段。Pack 指示在指定 LayoutKind.Sequential 值时应使用的打包大小,并控制结构中字段的对齐方式。托管结构的默认打包大小是 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];
}
With this code in place, I can now enumerate all of the streams in a file, as shown here:
有了这段代码,我现在可以枚举文件中的所有流,如下所示:
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);
}
}
}
You'll notice that this version of FileStreamSearcher returns more information than the version that uses FindFirstStreamW and FindNextStreamW. BackupRead can provide data on more than just the primary stream and Alternate Data Streams, also operating on streams containing security information, reparse data, and more. If you only want to see the Alternate Data Streams, you can filter based on the StreamInfo's Type property, which will be StreamType.AlternateData for Alternate Data Streams. To test this code, you can create a file that has Alternate Data Streams using the echo command at the command prompt:
您会注意到此版本的 FileStreamSearcher 比使用 FindFirstStreamW 和 FindNextStreamW 的版本返回更多信息。BackupRead 不仅可以提供有关主流和备用数据流的数据,还可以对包含安全信息、重新解析数据等的流进行操作。如果您只想查看备用数据流,则可以根据 StreamInfo 的 Type 属性进行过滤,对于备用数据流,该属性将是 StreamType.AlternateData。要测试此代码,您可以在命令提示符下使用 echo 命令创建一个包含备用数据流的文件:
> 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"
So, now you're able to retrieve the names of all Alternate Data Streams stored in a file. Great. But what if you want to actually manipulate the data in one of those streams? Unfortunately, if you attempt to pass a path for an Alternate Data Stream to one of the FileStream constructors, a NotSupportedException will be thrown: "The given path's format is not supported." To get around this, you can bypass FileStream's path canonicalization checks by directly accessing the CreateFile function exposed from kernel32.dll (see Figure 3). I've used a P/Invoke for the CreateFile function to open and retrieve a SafeFileHandle for the specified path, without performing any of the managed permission checks on the path, so it can include Alternate Data Stream identifiers. This SafeFileHandle is then used to create a new managed FileStream, providing the required access. With that in place, it's easy to manipulate the contents of an Alternate Data Stream using the System.IO namespace's functionality. The following example reads and prints out the contents of the C:\test.txt:magStream created in the previous example:
因此,现在您可以检索存储在文件中的所有备用数据流的名称。伟大的。但是,如果您想实际操作这些流之一中的数据怎么办?不幸的是,如果您尝试将备用数据流的路径传递给 FileStream 构造函数之一,则会抛出 NotSupportedException:“不支持给定路径的格式。” 为了解决这个问题,您可以通过直接访问从 kernel32.dll 公开的 CreateFile 函数来绕过 FileStream 的路径规范化检查(参见图 3)。我已经使用 P/Invoke 的 CreateFile 函数打开和检索指定路径的 SafeFileHandle,而不对路径执行任何托管权限检查,因此它可以包含备用数据流标识符。此 SafeFileHandle 然后用于创建新的托管 FileStream,提供所需的访问权限。有了它,就可以很容易地使用 System.IO 命名空间的功能来操作备用数据流的内容。下面的示例读取并打印出在上一个示例中创建的 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());
}
Figure?3?Using P/Invoke for CreateFile
图 3? 使用 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);
回答by Simon Mourier
This nuget package CodeFluent Runtime Clienthas (amongst other utilities) an NtfsAlternateStream Classthat supports create/read/update/delete/enumeration operations.
这个 nuget 包CodeFluent 运行时客户端(在其他实用程序中)有一个支持创建/读取/更新/删除/枚举操作的NtfsAlternateStream 类。