Подтвердить что ты не робот

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

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

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

4b9b3361

Ответ 1

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

Ответ 2

Вот версия для С#

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

Ответ 3

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

Ответ 4

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

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

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

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

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

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

Ответ 5

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

Если вы используете 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). Я объявил, что структура управляет аналогией как классом, а в сигнатуре interop я отметил, что она маршалируется как указатель на структуру. Последний параметр зарезервирован для использования в будущем и должен быть 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, это наносит ущерб маршалу, которому необходимо скопировать эти данные из неуправляемой памяти в управляемую память как часть вызова interop в 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. Все поля в первом экземпляре массива будут правильно выровнены. Например, поскольку поле "Размер" следует за двумя 32-битными целыми числами, это будет 8 байтов от начала массива, идеально подходящее для 8-байтового значения. Однако, если структура была размером 20 байтов, второй экземпляр в массиве не имел бы всех элементов, которые были бы правильно выровнены. Целочисленные значения будут все в порядке, но длинное значение будет 28 байт от начала массива, значение не равномерно делится на 8. Чтобы исправить это, компилятор накладывает структуру на размер 24, так что все поля всегда будут правильно выровнены (если предположить, что сам массив). Если компилятор поступает правильно, вам может быть интересно, почему я беспокоюсь об этом. Вы поймете, почему, если вы посмотрите на код на рисунке 2. Чтобы обойти первую проблему маршалинга, которую я описал, я фактически оставляю cStreamName из структуры Win32StreamID. Я использую BackupRead для чтения в достаточном количестве байтов, чтобы заполнить мою структуру Win32StreamID, а затем рассмотрю поле dwStreamNameSize структуры. Теперь, когда я знаю, как долго это имя, я снова могу использовать BackupRead для чтения в строковом значении из файла. Это все хорошо и dandy, но если Marshal.SizeOf возвращает 24 для моей структуры Win32StreamID вместо 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: "Данный формат пути не поддерживается". Чтобы обойти это, вы можете обойти проверку canonicalization пути FileStream путем прямого доступа к функции CreateFile, открытой из kernel32.dll(см. Рисунок 3). Я использовал функцию P/Invoke для CreateFile для открытия и получения 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 Toub в Журнал MSDN с января 2006 года.