c#-NTFS備用數據流-.NET

如何從.NET創建/刪除/讀取/寫入/ NTFS備用數據流?

如果沒有本機.NET支持,我將使用哪個Win32 API? 另外,由于我認為沒有記載,我將如何使用它們?

asked 2020-02-22T17:53:52Z
5個解決方案
32 votes

這是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
{
   
answered 2020-02-22T17:54:26Z
15 votes

此nuget包CodeFluent Runtime Client(除其他實用程序外)具有NtfsAlternateStream類,該類支持創建/讀取/更新/刪除/枚舉操作。

answered 2020-02-22T17:54:46Z
14 votes

沒有對它們的本機.NET支持。 您必須使用P / Invoke來調用本地Win32方法。

要創建它們,請使用諸如C:\some\directory:streamname之類的路徑調用CreateFile。如果您使用返回SafeFileHandle的互操作調用,則可以使用它來構造一個FileStream,然后可以對其進行讀寫。

要列出文件中存在的流,請使用FindFirstStreamW和FindNextStreamW(它們僅在Server 2003和更高版本上存在,而不在XP上存在)。

我不相信您可以刪除流,除非復制文件的其余部分并保留其中一個流。 將長度設置為0也許也可以,但是我還沒有嘗試過。

您還可以在目錄上具有備用數據流。 您可以使用與文件相同的方式來訪問它們-C:\some\directory:streamname

流可以具有獨立于默認流的壓縮,加密和稀疏性設置。

answered 2020-02-22T17:55:29Z
10 votes

首先,Microsoft®.NET Framework中沒有提供此功能。 如果需要,簡單明了,您需要直接或使用第三方庫進行某種互操作。

如果您使用的是Windows Server™2003或更高版本,則Kernel32.dll將對等對象提供給FindFirstFile和FindNextFile,以提供您要查找的確切功能。 FindFirstStreamW和FindNextStreamW允許您查找和枚舉特定文件中的所有備用數據流,檢索有關每個替代數據流的信息,包括其名稱和長度。 在托管代碼中使用這些功能的代碼與我在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,將目標文件的完整路徑傳遞給它。 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);
answered 2020-02-22T17:57:10Z
4 votes

不在.NET中:

[HTTP://support.Microsoft.com/恐怖/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);
   }
answered 2020-02-22T17:54:06Z
translate from