web-dev-qa-db-ja.com

NTFS代替データストリーム-.NET

.NETからNTFS代替データストリームを作成/削除/読み取り/書き込み/どのように作成しますか?

ネイティブ.NETサポートがない場合、どのWin32 APIを使用しますか?また、これは文書化されていないと思うので、どのように使用しますか?

47
user72491

.NETにはない:

http://support.Microsoft.com/kb/10576

#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);
   }
4
Otávio Décio

これは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;
}
32
JaredPar

このnugetパッケージ CodeFluentランタイムクライアント (他のユーティリティの中で) NtfsAlternateStream Class があり、作成/読み取り/更新/削除/列挙操作をサポートします。

14
Simon Mourier

それらに対するネイティブ.NETサポートはありません。ネイティブのWin32メソッドを呼び出すには、P/Invokeを使用する必要があります。

それらを作成するには、filename.txt:streamnameのようなパスを指定して CreateFile を呼び出します。 SafeFileHandleを返す相互運用呼び出しを使用する場合、それを使用して、読み取りと書き込みが可能なFileStreamを構築できます。

ファイルに存在するストリームを一覧表示するには、 FindFirstStreamW および FindNextStreamW を使用します(これらは、XPではなく、Server 2003以降にのみ存在します)。

残りのファイルをコピーしてストリームの1つを残さない限り、ストリームを削除できるとは思いません。長さを0に設定することも可能ですが、試したことがありません。

ディレクトリに代替データストリームを置くこともできます。ファイルと同じようにアクセスします-C:\some\directory:streamname

ストリームには、デフォルトのストリームとは無関係に、圧縮、暗号化、およびスパース性を設定できます。

14
Zack Elan

まず、Microsoft®.NET Frameworkには、この機能を提供するものはありません。必要に応じて、プレーンでシンプルな方法で、直接またはサードパーティのライブラリを使用して、何らかの相互運用を行う必要があります。

Windows Server™2003以降を使用している場合、Kernel32.dllは、目的の機能を提供するFindFirstFileおよびFindNextFileに対応するものを公開します。 FindFirstStreamWおよびFindNextStreamWを使用すると、特定のファイル内のすべての代替データストリームを検索して列挙し、名前や長さなど、それぞれに関する情報を取得できます。マネージコードからこれらの関数を使用するためのコードは、12月のコラムで示したコードと非常によく似ており、図1に示されています。

図1FindFirstStreamWおよび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の2番目のパラメーターは、返されるデータに必要な詳細レベルを指定します。現在、1つのレベル(FindStreamInfoStandard)のみがあり、数値は0です。関数の3番目のパラメーターは、WIN32_FIND_STREAM_DATA構造体へのポインターです(技術的に、3番目のパラメーターが指すものは、2番目のパラメーターの値によって決まります)情報レベルの詳細ですが、現在は1つのレベルしかないため、すべての意図と目的でこれはWIN32_FIND_STREAM_DATAです。その構造の対応するクラスをクラスとして宣言し、相互運用機能のシグネチャで、構造体へのポインタとしてマーシャリングするようにマークしました。最後のパラメーターは将来の使用のために予約されており、0である必要があります。FindFirstStreamWから有効なハンドルが返された場合、WIN32_FIND_STREAM_DATAインスタンスには、見つかったストリームに関する情報が含まれ、そのcStreamName値は、利用可能な最初のストリーム名として呼び出し元に返すことができます。 FindNextStreamWは、FindFirstStreamWから返されたハンドルを受け取り、提供されたWIN32_FIND_STREAM_DATAに、利用可能な次のストリームが存在する場合は、その情報を格納します。 FindNextStreamWは、別のストリームが利用可能な場合はtrueを返し、利用できない場合はfalseを返します。その結果、FindNextStreamWがfalseを返すまで、継続的にFindNextStreamWを呼び出し、結果のストリーム名を生成します。その場合は、最後のエラー値を再確認して、FindNextStreamWがストリームを使い果たしたために反復が停止したことを確認します。予期しない理由ではありません。残念ながら、Windows®XPまたはWindows 2000 Serverを使用している場合、これらの関数は使用できませんが、いくつかの代替策があります。最初の解決策は、現在エクスポートされていない文書化されていない関数です。 Kernel32.dll、NTQueryInformationFileから。ただし、文書化されていない関数は、理由により文書化されておらず、将来いつでも変更または削除することができます。使用しないことをお勧めします。この関数を使用する場合は、 Webを使用すると、多くの参照とサンプルのソースコードを見つけることができます。ただし、それは自己責任で行ってください。別の解決策、および図2で示した解決策は、 Kernel32.dllからエクスポートされた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);

図2BackupReadと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は、ターゲットファイルを構成する各代替データストリームに関する情報を見つけるのにも非常に便利です。ファイル内のすべてのデータを一連の個別のバイトストリームとして処理し(各代替データストリームはこれらのバイトストリームの1つです)、各ストリームの前に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は、前の4つのフィールドの後の構造体の残りのデータのアドレスにすぎません。つまり、構造体がsizeof(WIN32_STREAM_ID)バイトよりも大きくなるように割り当てられている場合、その余分なスペースは実際には、cStreamName配列の一部になります。前のフィールドdwStreamNameSizeは、配列の長さを正確に指定します。これはWin32開発に最適ですが、BackupReadへの相互運用呼び出しの一部として、このデータをアンマネージメモリからマネージメモリにコピーする必要があるマーシャラーに大打撃を与えます。マーシャラーは、WIN32_STREAM_ID構造体のサイズが可変であることを考えると、実際の大きさをどのように知るのですか?そうではありません。 2番目の問題は、パッキングと配置に関係しています。しばらく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バイトであると予想されます。ただし、次のコードを実行すると、両方の値が20ではなく24であることがわかります。

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

問題は、コンパイラーがこれらの構造体内の値が常に適切な境界に配置されるようにすることです。 4バイトの値は4で割り切れるアドレスにある必要があり、8バイトの値は8で割り切れる境界にある必要があります。ここで、Win32StreamID構造体の配列を作成するとどうなるかを想像してみてください。配列の最初のインスタンスのすべてのフィールドが適切に配置されます。たとえば、サイズフィールドは2つの32ビット整数に続くため、配列の先頭から8バイトとなり、8バイトの値に最適です。ただし、構造のサイズが20バイトの場合、配列の2番目のインスタンスでは、すべてのメンバーが適切に配置されません。整数値はすべて問題ありませんが、長い値は配列の先頭から28バイトであり、8で割り切れない値になります。これを修正するために、コンパイラーは構造体を24のサイズにパディングします。フィールドは常に適切に配置されます(配列自体がそうであると仮定)。コンパイラが正しいことをしているのなら、なぜ私がこれについて心配しているのか疑問に思われるかもしれません。図2のコードを見ると、その理由がわかります。私が説明した最初のマーシャリングの問題を回避するために、実際にはcStreamNameをWin32StreamID構造体から除外しています。 BackupReadを使用して、Win32StreamID構造を満たすのに十分なバイト数を読み取ってから、構造のdwStreamNameSizeフィールドを調べます。名前の長さがわかったので、再びBackupReadを使用して、ファイルから文字列の値を読み込むことができます。これで問題は解決しましたが、Marshal.SizeOfがWin32StreamID構造体に対して20ではなく24を返す場合、あまりにも多くのデータを読み取ろうとしています。これを回避するには、Win32StreamIDのサイズが実際に24ではなく20であることを確認する必要があります。これは、構造を装飾するStructLayoutAttributeのフィールドを使用して2つの異なる方法で実現できます。 1つは、サイズフィールドを使用することです。これは、構造体の大きさを正確にランタイムに指示します。

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

2番目のオプションは、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];
}

このコードを配置すると、次に示すように、ファイル内のすべてのストリームを列挙できます。

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"

これで、ファイルに保存されているすべての代替データストリームの名前を取得できるようになりました。すごい。しかし、これらのストリームの1つで実際にデータを操作したい場合はどうでしょうか。残念ながら、代替データストリームのパスをFileStreamコンストラクターの1つに渡そうとすると、NotSupportedExceptionがスローされます。「指定されたパスの形式はサポートされていません。」これを回避するには、kernel32.dllから公開されているCreateFile関数に直接アクセスして、FileStreamのパス正規化チェックをバイパスします(図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());
}

図3CreateFileに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 ToubMSDN Magazine from 2006年1月

10
Václav Dajbych