Flujos de datos alternativos NTFS – .NET

¿Cómo crearía / eliminaría / leería / escribiría / NTFS secuencias de datos alternativas de .NET?

Si no hay soporte para .NET nativo, ¿qué API de Win32 usaría? Además, ¿cómo los usaría, ya que no creo que esto esté documentado?

No en .NET:

http://support.microsoft.com/kb/105763

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

Aquí hay una versión para 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; } 

Este paquete nuget CodeFluent Runtime Client tiene (entre otras utilidades) una clase NtfsAlternateStream que admite operaciones de creación / lectura / actualización / eliminación / enumeración.

No hay soporte nativo de .NET para ellos. Tienes que usar P / Invoke para llamar a los métodos nativos de Win32.

Para crearlos, llame a CreateFile con una ruta de acceso como filename.txt:streamname . Si usa la llamada de interoperabilidad que devuelve un SafeFileHandle, puede usarlo para construir un FileStream que luego podrá leer y escribir.

Para enumerar las secuencias que existen en un archivo, use FindFirstStreamW y FindNextStreamW (que existen solo en Server 2003 y versiones posteriores, no en XP).

No creo que pueda eliminar una transmisión, excepto copiando el rest del archivo y dejando fuera una de las transmisiones. Establecer la longitud en 0 también puede funcionar, pero no lo he intentado.

También puede tener flujos de datos alternativos en un directorio. Puede acceder a ellos de la misma manera que con los archivos: C:\some\directory:streamname .

Las transmisiones pueden tener compresión, encriptación y dispersión establecidas independientemente de la secuencia predeterminada.

R Primero, nada en Microsoft® .NET Framework proporciona esta funcionalidad. Si lo desea, simple y llanamente necesitará hacer algún tipo de interoperabilidad, ya sea directamente o usando una biblioteca de terceros.

Si está utilizando Windows Server ™ 2003 o posterior, Kernel32.dll expone las contrapartes de FindFirstFile y FindNextFile que proporcionan la funcionalidad exacta que está buscando. FindFirstStreamW y FindNextStreamW le permiten buscar y enumerar todos los flujos de datos alternativos dentro de un archivo particular, recuperando información sobre cada uno, incluidos su nombre y su longitud. El código para usar estas funciones del código administrado es muy similar al que mostré en mi columna de diciembre, y se muestra en la Figura 1.

Figura 1 Usando FindFirstStreamW y 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 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(); } } } 

Simplemente llame a FindFirstStreamW, pasando a la ruta completa al archivo de destino. El segundo parámetro para FindFirstStreamW dicta el nivel de detalle que desea en los datos devueltos; actualmente, solo hay un nivel (FindStreamInfoStandard), que tiene un valor numérico de 0. El tercer parámetro de la función es un puntero a una estructura WIN32_FIND_STREAM_DATA (técnicamente, lo que el tercer parámetro señala está dictado por el valor del segundo parámetro detallando el nivel de información, pero como actualmente solo hay un nivel, para todos los efectos este es un WIN32_FIND_STREAM_DATA). He declarado esa contraparte administrada de la estructura como una clase, y en la firma de interoperabilidad que he marcado para que se marque como un puntero a una estructura. El último parámetro está reservado para uso futuro y debe ser 0. Si se devuelve un identificador válido de FindFirstStreamW, la instancia WIN32_FIND_STREAM_DATA contiene información sobre la secuencia encontrada y su valor de cStreamName se puede devolver al llamador como el primer nombre de secuencia disponible. FindNextStreamW acepta el identificador devuelto por FindFirstStreamW y rellena el WIN32_FIND_STREAM_DATA proporcionado con información sobre la siguiente secuencia disponible, si existe. FindNextStreamW devuelve verdadero si hay otra secuencia disponible, o falso si no. Como resultado, continuamente invoco FindNextStreamW y obtengo el nombre de la secuencia resultante hasta que FindNextStreamW devuelve falso. Cuando eso sucede, compruebo dos veces el último valor de error para asegurarme de que la iteración se detuvo porque FindNextStreamW se quedó sin transmisiones, y no por algún motivo inesperado. Desafortunadamente, si está usando Windows® XP o Windows 2000 Server, estas funciones no están disponibles para usted, pero hay un par de alternativas. La primera solución implica una función no documentada actualmente exportada desde Kernel32.dll, NTQueryInformationFile. Sin embargo, las funciones no documentadas no están documentadas por algún motivo, y se pueden cambiar o incluso eliminar en cualquier momento en el futuro. Lo mejor es no usarlos. Si desea utilizar esta función, busque en la Web y encontrará muchas referencias y ejemplos de código fuente. Pero hazlo bajo tu propio riesgo. Otra solución, y que he demostrado en la Figura 2 , se basa en dos funciones exportadas desde Kernel32.dll, y están documentadas. Como sus nombres implican, BackupRead y BackupSeek son parte de la API Win32® para soporte de respaldo:

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

Figura 2 Uso de BackupRead y 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 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(); } } } } 

La idea detrás de BackupRead es que se puede utilizar para leer datos de un archivo en un búfer, que luego se puede escribir en el medio de almacenamiento de copia de seguridad. Sin embargo, BackupRead también es muy útil para buscar información sobre cada uno de los flujos de datos alternativos que componen el archivo de destino. Procesa todos los datos del archivo como una serie de flujos de bytes discretos (cada flujo de datos alternativo es uno de estos flujos de bytes), y cada una de las secuencias va precedida por una estructura WIN32_STREAM_ID. Por lo tanto, para enumerar todas las secuencias, simplemente necesita leer todas estas estructuras WIN32_STREAM_ID desde el comienzo de cada secuencia (aquí es donde BackupSeek se vuelve muy útil, ya que puede usarse para saltar de una secuencia a otra sin tener que para leer todos los datos en el archivo). Para comenzar, primero necesita crear una contraparte administrada para la estructura WIN32_STREAM_ID no administrada:

 typedef struct _WIN32_STREAM_ID { DWORD dwStreamId; DWORD dwStreamAttributes; LARGE_INTEGER Size; DWORD dwStreamNameSize; WCHAR cStreamName[ANYSIZE_ARRAY]; } WIN32_STREAM_ID; 

En su mayor parte, esto es como cualquier otra estructura que reunirías a través de P / Invoke. Sin embargo, hay algunas complicaciones. En primer lugar, WIN32_STREAM_ID es una estructura de tamaño variable. Su último miembro, cStreamName, es una matriz con longitud ANYSIZE_ARRAY. Mientras que ANYSIZE_ARRAY se define como 1, cStreamName es solo la dirección del rest de los datos en la estructura después de los cuatro campos anteriores, lo que significa que si la estructura se asigna para ser mayor que sizeof (WIN32_STREAM_ID) bytes, ese espacio adicional de hecho, ser parte de la matriz cStreamName. El campo anterior, dwStreamNameSize, especifica exactamente cuánto dura la matriz. Si bien esto es excelente para el desarrollo de Win32, causa esgulps en un contador de referencias que necesita copiar estos datos de la memoria no administrada a la memoria administrada como parte de la llamada de interoperabilidad a BackupRead. ¿Cómo sabe el apuntador qué tan grande es en realidad la estructura WIN32_STREAM_ID dado que es de tamaño variable? No es así El segundo problema tiene que ver con el empaque y la alineación. Ignorando cStreamName por un momento, considere la siguiente posibilidad para su contraparte WIN32_STREAM_ID administrada:

 [StructLayout(LayoutKind.Sequential)] public struct Win32StreamID { public int dwStreamId; public int dwStreamAttributes; public long Size; public int dwStreamNameSize; } 

Un Int32 tiene 4 bytes de tamaño y un Int64 tiene 8 bytes. Por lo tanto, es de esperar que esta estructura tenga 20 bytes. Sin embargo, si ejecuta el siguiente código, encontrará que ambos valores son 24, no 20:

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

El problema es que el comstackdor quiere asegurarse de que los valores dentro de estas estructuras siempre estén alineados en el límite apropiado. Los valores de cuatro bytes deben estar en direcciones divisibles por 4, los valores de 8 bytes deben estar en los límites divisibles por 8, y así sucesivamente. Ahora imagine lo que sucedería si fuera a crear una matriz de estructuras Win32StreamID. Todos los campos en la primera instancia de la matriz se alinearán correctamente. Por ejemplo, dado que el campo Tamaño sigue dos enteros de 32 bits, serían 8 bytes desde el inicio de la matriz, perfecto para un valor de 8 bytes. Sin embargo, si la estructura tiene un tamaño de 20 bytes, la segunda instancia de la matriz no tendrá todos sus miembros alineados correctamente. Los valores enteros estarían todos bien, pero el valor largo sería 28 bytes desde el inicio de la matriz, un valor no divisible equitativamente por 8. Para solucionar esto, el comstackdor ajusta la estructura a un tamaño de 24, de modo que todos los campos siempre estarán alineados correctamente (suponiendo que la matriz en sí). Si el comstackdor está haciendo lo correcto, tal vez se pregunte por qué me preocupa esto. Verá por qué si mira el código de la Figura 2. Para evitar el primer problema de cálculo de referencias que describí, de hecho, dejo el cStreamName fuera de la estructura de Win32StreamID. Utilizo BackupRead para leer en suficientes bytes para llenar mi estructura Win32StreamID, y luego examino el campo dwStreamNameSize de la estructura. Ahora que sé cuánto tiempo dura el nombre, puedo usar BackupRead nuevamente para leer el valor de la cadena desde el archivo. Eso está muy bien, pero si Marshal.SizeOf devuelve 24 para mi estructura Win32StreamID en lugar de 20, trataré de leer demasiados datos. Para evitar esto, necesito asegurarme de que el tamaño de Win32StreamID sea de hecho 20 y no 24. Esto se puede lograr de dos maneras diferentes usando campos en StructLayoutAttribute que adorna la estructura. El primero es usar el campo Tamaño, que dicta al motor de ejecución exactamente qué tan grande debe ser la estructura:

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

La segunda opción es usar el campo Pack. El paquete indica el tamaño de embalaje que se debe usar cuando se especifica el valor LayoutKind.Sequential y controla la alineación de los campos dentro de la estructura. El tamaño de embalaje predeterminado para una estructura administrada es 8. Si cambio eso a 4, obtengo la estructura de 20 bytes que estoy buscando (y como en realidad no la estoy usando en una matriz, no pierdo eficacia). o la estabilidad que podría resultar de dicho cambio de empaque):

 [StructLayout(LayoutKind.Sequential, Pack = 4)] public struct Win32StreamID { public StreamType dwStreamId; public int dwStreamAttributes; public long Size; public int dwStreamNameSize; // WCHAR cStreamName[1]; } 

Con este código en su lugar, ahora puedo enumerar todas las secuencias en un archivo, como se muestra aquí:

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

Notará que esta versión de FileStreamSearcher devuelve más información que la versión que usa FindFirstStreamW y FindNextStreamW. BackupRead puede proporcionar datos en más que solo la transmisión principal y los flujos de datos alternativos, que también operan en transmisiones que contienen información de seguridad, datos de análisis y más. Si solo desea ver los flujos de datos alternativos, puede filtrar en función de la propiedad Tipo de StreamInfo, que será StreamType.AlternateData para flujos de datos alternativos. Para probar este código, puede crear un archivo que tenga flujos de datos alternativos utilizando el comando echo en el símbolo del sistema:

 > 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" 

Entonces, ahora puede recuperar los nombres de todos los flujos de datos alternativos almacenados en un archivo. Estupendo. Pero, ¿y si realmente quiere manipular los datos en una de esas secuencias? Desafortunadamente, si intenta pasar una ruta para un flujo de datos alternativo a uno de los constructores de FileStream, se lanzará una NotSupportedException: "El formato de la ruta dada no es compatible". Para evitar esto, puede omitir las comprobaciones de canonicalización de ruta de FileStream accediendo directamente a la función CreateFile expuesta desde kernel32.dll (consulte la Figura 3 ). He usado una P / Invoke para la función CreateFile para abrir y recuperar un SafeFileHandle para la ruta especificada, sin realizar ninguna de las comprobaciones de permisos administrados en la ruta, por lo que puede incluir identificadores Alternate Data Stream. A continuación, este SafeFileHandle se utiliza para crear un nuevo FileStream administrado, que proporciona el acceso requerido. Con eso en su lugar, es fácil manipular los contenidos de un flujo de datos alternativo utilizando la funcionalidad del espacio de nombres System.IO. El siguiente ejemplo lee e imprime el contenido de C: \ test.txt: magStream creado en el ejemplo anterior:

 string path = @"C:\test.txt:magStream"; using (StreamReader reader = new StreamReader(CreateFileStream(path, FileAccess.Read, FileMode.Open, FileShare.Read))) { Console.WriteLine(reader.ReadToEnd()); } 

Figura 3 Usando P / Invoke para 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 en MSDN Magazine desde enero de 2006 .