2009-03-03 78 views
42

如何从.NET创建/删除/读取/写入/ NTFS备用数据流?NTFS备用数据流-NET

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

+0

顺便说一句,如果你想复制文件与标准文件复制进度对话框,你不能使用:: SHFileOperation() - 它根本不适用于AltDataStreams(在Windows 7上检查)。 对于:: CopyFileEx(),它在某些情况下有效(例如,它可以在调用进度回调时将文件复制到AltDataStream中),但在其他情况下不起作用。 – Nishi 2009-11-30 10:53:24

回答

4

不要在.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); 
    } 
+8

两个缺少CloseHandle调用...操作系统将清理,但在实际应用程序中会出现问题。 – Richard 2009-03-03 10:16:46

+3

@Richard - 刚刚从MS的支持站点复制... – 2009-03-03 12:47:34

+1

ávio您可以从C#中对这些函数进行P/Invoke。 – 2010-11-29 12:14:00

30

这里是一个版本的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; 
} 
+8

此处应使用派生于SafeHandle的类型,以确保清理这些文件句柄。 – Richard 2009-03-03 10:18:16

+7

您展示了如何使用本机API,但没有展示如何使用从`CreateFileW`返回的指针。我真的很希望看到一个更完整的示例,它写入Windows资源管理器中文件属性的摘要选项卡中的常用属性。 – 2013-03-19 13:45:39

13

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

要创建它们,请拨打CreateFile,路径为filename.txt:streamname。如果您使用返回SafeFileHandle的interop调用,则可以使用它构造一个FileStream,然后您可以读取&。

要列出文件中存在的流,请使用FindFirstStreamWFindNextStreamW(仅在Server 2003和更高版本上存在 - 不是XP)。

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

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

流可以对它们设置压缩,加密和稀疏性,而与默认流无关。

6

A首先,Microsoft®.NET Framework没有提供此功能。如果你想要它,简单而简单,你需要直接或使用第三方库进行某种互操作。

如果您使用的是Windows Server™2003或更高版本,则Kernel32.dll会向对应方提供FindFirstFile和FindNextFile,以提供您正在查找的确切功能。 FindFirstStreamW和FindNextStreamW允许您查找和枚举特定文件中的所有备用数据流,检索有关每个备用数据流的信息,包括其名称和长度。使用托管代码这些函数的代码非常相似,我在我的十二月列显示,和图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,传递给目标文件的完整路径。 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 Server,这些功能对您而言并不可用,但有几种选择。第一种解决方案涉及当前从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); 

图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对于查找构成目标文件的每个备用数据流的信息也非常方便。它将文件中的所有数据处理为一系列离散的字节流(每个备用数据流是这些字节流之一),并且每个数据流前面都有一个WIN32_STREAM_ID结构。因此,为了枚举所有的流,您只需要从每个流的开头读取所有这些WIN32_STREAM_ID结构(这是BackupSeek变得非常方便的地方,因为它可以用于从流跳转到流而不用读取文件中的所有数据)。 要开始,您首先需要创建一个管理对应的非托管WIN32_STREAM_ID结构:

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开发非常有用,但它对于需要将此数据从非托管内存复制到托管内存的封送处理器造成严重破坏,作为对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; 
} 

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

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

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

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

第二种选择是使用包装领域。 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]; 
} 

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

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不仅可以提供主流和备用数据流的数据,还可以在包含安全信息,重新分析数据等的流上进行操作。如果您只想查看备用数据流,则可以根据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" 

所以,现在你可以检索存储在所有备用数据流的名称一份文件。大。但是如果你想实际操纵其中一个流中的数据呢?不幸的是,如果您尝试将替代数据流的路径传递给FileStream构造函数之一,则会引发NotSupportedException:“不支持给定路径的格式。” 要解决这个问题,可以通过直接访问从kernel32公开的CreateFile函数来绕过FileStream的路径规范化检查。dll(见图3)。我已经使用CreateFile函数的P/Invoke为指定路径打开并检索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()); 
} 

图3使用P/Invoke为的CreateFile

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 ToubMSDN Magazine from January 2006