代码之家  ›  专栏  ›  技术社区  ›  user72491

如何使用读取和修改NTFS备用数据流。NET[关闭]

  •  54
  • user72491  · 技术社区  · 17 年前

    似乎没有本地人。NET支持。我将使用哪种Win32 API?此外,我该如何使用它们,因为我认为这没有记录在案?

    5 回复  |  直到 4 年前
        1
  •  33
  •   Alex Essilfie    14 年前

    这是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;
    }
    
        2
  •  18
  •   Carl Reinke Václav Dajbych    8 年前

    首先,微软什么都没有。NET Framework提供了此功能。如果你想要它,简单明了,你需要直接或使用第三方库进行某种互操作。

    如果您使用的是Windows Server 2003或更高版本,Kernel32.dll会向FindFirstFile和FindNextFile公开对应项,以提供您要查找的确切功能。FindFirstStreamW和FindNextStreamW允许您查找和枚举特定文件中的所有备用数据流,检索每个数据流的信息,包括其名称和长度。从托管代码中使用这些函数的代码与我在12月份的专栏中展示的代码非常相似,如图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_FILEND_STREAM.DATA)。我已经将该结构的托管对应项声明为类,并在互操作签名中将其标记为封送为指向结构的指针。最后一个参数保留供将来使用,应为0。 如果从FindFirstStreamW返回有效句柄,则WIN32_FIND_STREAM_DATA实例包含有关找到的流的信息,其cStreamName值可以作为第一个可用流名称返回给调用者。FindNextStreamW接受FindFirstStreamW返回的句柄,并在提供的WIN32_FIND_STREAM_DATA中填充有关下一个可用流的信息(如果存在)。如果另一个流可用,则FindNextStreamW返回true,否则返回false。 因此,我不断调用FindNextStreamW并生成结果流名称,直到FindNextStreamW返回false。当这种情况发生时,我会仔细检查最后一个错误值,以确保迭代停止是因为FindNextStreamW流用尽,而不是出于某种意外原因。 图2:

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

    使用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结构的大小是可变的,编组器如何知道它实际上有多大?它没有。

    [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个字节,则数组中的第二个实例将不会正确对齐其所有成员。整数值都可以,但长值将是从数组开始的28个字节,这个值不能被8整除。为了解决这个问题,编译器将结构填充到24的大小,这样所有字段都将始终正确对齐(假设数组本身是)。 如果编译器正在做正确的事情,你可能会想知道我为什么担心这个。如果您查看图2中的代码,您将看到原因。为了解决我描述的第一个封送处理问题,我确实将cStreamName排除在Win32StreamID结构之外。我使用BackupRead读取足够的字节来填充我的Win32StreamID结构,然后检查该结构的dwStreamNameSize字段。现在我知道这个名称有多长了,我可以再次使用BackupRead从文件中读取字符串的值。这一切都很好,但如果元帅。SizeOf为我的Win32StreamID结构返回24,而不是20,我将尝试读取太多数据。

    [StructLayout(LayoutKind.Sequential, Size = 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。备用数据流的备用数据。

    > 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:“不支持给定路径的格式。” 图3 ).我为CreateFile函数使用了P/Invoke来打开和检索指定路径的SafeFileHandle,而无需对路径执行任何托管权限检查,因此它可以包含备用数据流标识符。然后,使用此SafeFileHandle创建新的托管FileStream,提供所需的访问权限。有了这个,就可以使用系统轻松操纵备用数据流的内容。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());
    }
    

    使用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 在……里面 MSDN Magazine from January 2006 .

        3
  •  17
  •   mrid    7 年前

    CreateFile 有一条路像 filename.txt:streamname 。如果使用返回SafeFileHandle的互操作调用,则可以使用它来构造FileStream,然后可以读取&写信给。

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

    我认为你不能删除一个流,除非复制文件的其余部分并删除其中一个流。将长度设置为0也可能有效,但我还没有尝试过。

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

    流可以独立于默认流设置压缩、加密和稀疏性。

        4
  •  15
  •   Simon Mourier    13 年前

    这个nuget包 CodeFluent Runtime Client (除其他公用事业外) NtfsAlternateStream Class

        5
  •  4
  •   Otávio Décio    17 年前

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