Альтернативные потоки данных NTFS-.NET [закрыто]

Как создать/ удалить/ прочитать/ записать / NTFS альтернативные потоки данных из .NET?

Если нет собственной поддержки .NET, какой Win32 API я бы использовал? Кроме того, как бы я их использовал, поскольку я не думаю, что это документировано?

5 ответов


Не в .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);
   }

вот версия для 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;
}

этот пакет nuget CodeFluent Runtime Client имеет (среди других утилит)Класс NtfsAlternateStream который поддерживает операции создания/чтения/обновления/удаления / перечисления.


для них нет собственной поддержки .NET. Вы должны использовать P / Invoke для вызова собственных методов Win32.

чтобы создать их, вызовите CreateFile С путем, как filename.txt:streamname. Если вы используете вызов interop, который возвращает SafeFileHandle, вы можете использовать его для создания FileStream, который затем можно читать и писать.

чтобы перечислить потоки, существующие в файле, используйте FindFirstStreamW и FindNextStreamW (которые существуют только на Server 2003 и более поздних версий-не XP).

Я не верю, что вы можете удалить поток, кроме как скопировав остальную часть файла и оставив один из потоков. Установка длины в 0 также может работать,но я не пробовал.

вы также можете иметь альтернативные потоки данных в каталоге. Вы получаете доступ к ним так же, как с файлами - C:\some\directory:streamname.

потоки могут иметь сжатие, шифрование и разреженность, установленные на них независимо от потока по умолчанию.


во-первых, ничто в Microsoft® .NET Framework не предоставляет эту функциональность. Если вы хотите этого, просто и просто вам нужно будет сделать какое-то взаимодействие, либо напрямую, либо с помощью сторонней библиотеки.

если вы используете Windows Server™ 2003 или более поздней версии, Kernel32.dll предоставляет аналоги FindFirstFile и FindNextFile, которые обеспечивают точную функциональность, которую вы ищете. FindFirstStreamW и FindNextStreamW позволяют найти и перечислить все альтернативные данные Потоки в пределах определенного файла, извлекая информацию о каждом, включая его имя и его длину. Код для использования этих функций из управляемого кода очень похож на тот, который я показывал в моей декабрьской колонке, и показан на рисунке 1.

Рис. 1 использование Findfirstststreamw и 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();
        }
    }
}

вы просто вызываете Findfirstststreamw, передавая ему полный путь к целевому файлу. Второй параметр 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 байт, второй экземпляр массива не имел бы всех своих членов правильно выровненными. Целочисленные значения будут в порядке, но длинное значение будет 28 байт от начала массива, значение не делится без остатка на 8. Чтобы исправить это, компилятор помещает структуру в размер 24, так что все поля всегда будут правильно выровнены (при условии, что сам массив). Если компилятор поступает правильно, вам может быть интересно, почему меня это беспокоит. Вы поймете почему, если посмотрите на код на Рис. 2. Чтобы обойти первую проблему маршалинга, которую я описал, я на самом деле оставляю cStreamName из структура Win32StreamID. Я использую BackupRead для чтения достаточного количества байтов, чтобы заполнить мою структуру Win32StreamID, а затем я изучаю поле dwStreamNameSize структуры. Теперь, когда я знаю, как долго имя, я могу использовать BackupRead снова, чтобы прочитать значение строки из файла. Это все хорошо и денди, но если Маршал.SizeOf возвращает 24 для моей структуры Win32StreamID вместо 20, я буду пытаться прочитать слишком много данных. Чтобы избежать этого, мне нужно убедиться, что размер Win32StreamID находится в то 20, а не 24. Это можно сделать двумя различными способами, используя поля в StructLayoutAttribute, который украшает структуру. Первый-использовать поле Size, которое диктует среде выполнения, насколько большой должна быть структура:

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

второй вариант-использовать поле Pack. Pack указывает размер упаковки, который должен использоваться при LayoutKind.Последовательное значение задается и управляет выравниванием полей внутри структуры. Неисполнение размер упаковки для управляемой структуры 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, которое будет 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: "формат данного пути не поддерживается." Чтобы обойти это, вы можете обойти проверки канонизации пути FileStream с помощью прямой доступ к функции CreateFile, предоставляемой из kernel32.dll (см. Рисунок 3). Я использовал P / Invoke для функции CreateFile, чтобы открыть и получить SafeFileHandle для указанного пути, не выполняя никаких проверок управляемых разрешений на пути, поэтому он может включать альтернативные идентификаторы потока данных. Этот SafeFileHandle затем используется для создания нового управляемого потока файлов, обеспечивая необходимый доступ. С этим на месте, легко манипулировать содержимым Альтернативный поток данных с помощью системы.Функциональность пространства имен 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);

Стивен Toub на журнал MSDN с января 2006 года.