Skip to content

Commit c28c2c4

Browse files
author
dahall
committed
Added ReadBackup overload that does the work of pulling each stream out of a file with test.
1 parent c543122 commit c28c2c4

File tree

2 files changed

+131
-3
lines changed

2 files changed

+131
-3
lines changed

PInvoke/Kernel32/WinBase.Backup.cs

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
namespace Vanara.PInvoke;
1+
using System.Collections.Generic;
2+
3+
namespace Vanara.PInvoke;
24

35
public static partial class Kernel32
46
{
@@ -470,6 +472,105 @@ public enum TAPEMARK_TYPE
470472
public static extern bool BackupRead([In] HFILE hFile, IntPtr lpBuffer, uint nNumberOfBytesToRead, out uint lpNumberOfBytesRead, [MarshalAs(UnmanagedType.Bool)] bool bAbort,
471473
[MarshalAs(UnmanagedType.Bool)] bool bProcessSecurity, ref IntPtr lpContext);
472474

475+
/// <summary>
476+
/// The <c>BackupRead</c> function can be used to back up a file or directory, including the security information. The function reads
477+
/// data associated with a specified file or directory into a buffer, which can then be written to the backup medium using the
478+
/// <c>WriteFile</c> function.
479+
/// <para>This method appropriately closes the read operation after reading all the streams.</para>
480+
/// </summary>
481+
/// <param name="hFile">
482+
/// <para>
483+
/// Handle to the file or directory to be backed up. To obtain the handle, call the <c>CreateFile</c> function. The SACLs are not read
484+
/// unless the file handle was created with the <c>ACCESS_SYSTEM_SECURITY</c> access right. For more information, see File Security and
485+
/// Access Rights.
486+
/// </para>
487+
/// <para>
488+
/// The handle must be synchronous (nonoverlapped). This means that the FILE_FLAG_OVERLAPPED flag must not be set when <c>CreateFile</c>
489+
/// is called. This function does not validate that the handle it receives is synchronous, so it does not return an error code for a
490+
/// synchronous handle, but calling it with an asynchronous (overlapped) handle can result in subtle errors that are very difficult to debug.
491+
/// </para>
492+
/// <para>
493+
/// The <c>BackupRead</c> function may fail if <c>CreateFile</c> was called with the flag <c>FILE_FLAG_NO_BUFFERING</c>. In this case,
494+
/// the <c>GetLastError</c> function returns the value <c>ERROR_INVALID_PARAMETER</c>.
495+
/// </para>
496+
/// </param>
497+
/// <param name="bProcessSecurity">
498+
/// <para>Indicates whether the function will restore the access-control list (ACL) data for the file or directory.</para>
499+
/// <para>If bProcessSecurity is <c>TRUE</c>, the ACL data will be backed up.</para>
500+
/// </param>
501+
/// <param name="retrieveContents">
502+
/// If set to <see langword="true"/>, the contents of each stream will be returned in <paramref name="lpBuffers"/>; otherwise <see
503+
/// langword="false"/> will return a null buffer.
504+
/// </param>
505+
/// <param name="lpBuffers">Returns a list of tuples with each stream's information (as a <see cref="WIN32_STREAM_ID"/> value, and optionally the contents.</param>
506+
/// <returns>
507+
/// <para>If the function succeeds, the return value is <see cref="Win32Error.NO_ERROR"/>.</para>
508+
/// <para>If the function fails, the return value indicates which error occurred.</para>
509+
/// </returns>
510+
[PInvokeData("Winbase.h", MSDNShortId = "aa362509")]
511+
public static Win32Error BackupRead([In] HFILE hFile, [Optional] bool bProcessSecurity, [Optional] bool retrieveContents, out List<(WIN32_STREAM_ID id, SafeAllocatedMemoryHandle? buffer)> lpBuffers)
512+
{
513+
lpBuffers = [];
514+
unsafe
515+
{
516+
// Use header to prevent unknown allocation of name value
517+
WIN32_STREAM_ID_HEADER hdr = new();
518+
var hdrSize = (uint)sizeof(WIN32_STREAM_ID_HEADER);
519+
520+
IntPtr lpContext = default;
521+
try
522+
{
523+
while (true)
524+
{
525+
// Read the next stream header:
526+
var ret = BackupRead(hFile, (IntPtr)(void*)&hdr, hdrSize, out var bytesRead, false, bProcessSecurity, ref lpContext);
527+
if (!ret)
528+
return GetLastError();
529+
// Last stream found, so exit loop
530+
if (bytesRead == 0)
531+
break;
532+
// Unexpected error -- this should always be right
533+
if (bytesRead != hdrSize)
534+
return Win32Error.ERROR_INVALID_DATA;
535+
536+
// Get the name if available
537+
string? name = null;
538+
if (hdr.dwStreamNameSize > 0)
539+
{
540+
using SafeLPWSTR pName = new(((int)hdr.dwStreamNameSize / 2) + 1);
541+
bool nameRead = BackupRead(hFile, pName, hdr.dwStreamNameSize, out bytesRead, false, bProcessSecurity, ref lpContext);
542+
if (!nameRead)
543+
return GetLastError();
544+
name = pName;
545+
}
546+
547+
// Capture the details about the stream and allocated memory with the contents
548+
(WIN32_STREAM_ID id, SafeAllocatedMemoryHandle? buffer) entry = (hdr, null);
549+
entry.id.cStreamName = name ?? "";
550+
if (hdr.dwStreamId != BACKUP_STREAM_ID.BACKUP_INVALID && retrieveContents)
551+
{
552+
var buf = new SafeHGlobalHandle(hdr.Size.LowPart());
553+
if (buf.Size > 0 && !BackupRead(hFile, buf, buf.Size, out bytesRead, false, bProcessSecurity, ref lpContext))
554+
return GetLastError();
555+
entry.buffer = buf;
556+
}
557+
else
558+
{
559+
if (!BackupSeek(hFile, hdr.Size.LowPart(), (uint)hdr.Size.HighPart(), out _, out _, ref lpContext))
560+
return GetLastError();
561+
}
562+
lpBuffers.Add(entry);
563+
}
564+
}
565+
finally
566+
{
567+
// Close the backup
568+
BackupRead(hFile, IntPtr.Zero, 0, out _, true, bProcessSecurity, ref lpContext);
569+
}
570+
}
571+
return 0;
572+
}
573+
473574
/// <summary>
474575
/// The <c>BackupSeek</c> function seeks forward in a data stream initially accessed by using the <c>BackupRead</c> or <c>BackupWrite</c> function.
475576
/// </summary>
@@ -1881,6 +1982,12 @@ public struct WIN32_STREAM_ID
18811982
/// <summary>Unicode string that specifies the name of the alternative data stream.</summary>
18821983
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 1)]
18831984
public string cStreamName;
1985+
1986+
/// <summary>Performs an implicit conversion from <see cref="WIN32_STREAM_ID_HEADER"/> to <see cref="WIN32_STREAM_ID"/>.</summary>
1987+
/// <param name="h">The header.</param>
1988+
/// <returns>The result of the conversion.</returns>
1989+
public static implicit operator WIN32_STREAM_ID(in WIN32_STREAM_ID_HEADER h) =>
1990+
new() { dwStreamId = h.dwStreamId, dwStreamAttributes = h.dwStreamAttributes, Size = h.Size, dwStreamNameSize = h.dwStreamNameSize };
18841991
}
18851992

18861993
/// <summary>The <c>WIN32_STREAM_ID</c> structure contains stream data.</summary>

UnitTests/PInvoke/Kernel32/WinBase.BackupTests.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using NUnit.Framework;
2+
using System.Collections.Generic;
3+
using System.Linq;
24
using static Vanara.PInvoke.Kernel32;
35

46
namespace Vanara.PInvoke.Tests;
@@ -12,9 +14,10 @@ public void BackupImportTest()
1214
// No tape drives to test against, so just checking each method runs.
1315
Assert.That(() =>
1416
{
15-
BackupRead(HFILE.NULL, default, 0, out _, false, false, out IntPtr ctx);
17+
IntPtr ctx = default;
18+
Kernel32.BackupRead(HFILE.NULL, default, 0, out _, false, false, ref ctx);
1619
BackupSeek(HFILE.NULL, 0, 0, out _, out _, ref ctx);
17-
BackupWrite(HFILE.NULL, default, 0, out _, false, false, out ctx);
20+
BackupWrite(HFILE.NULL, default, 0, out _, false, false, ref ctx);
1821
CreateTapePartition(HFILE.NULL, TAPE_PARTITION_METHOD.TAPE_FIXED_PARTITIONS, 1, 1);
1922
EraseTape(HFILE.NULL, TAPE_ERASE_TYPE.TAPE_ERASE_SHORT, true);
2023
uint sz = 0;
@@ -27,4 +30,22 @@ public void BackupImportTest()
2730
WriteTapemark(HFILE.NULL, TAPEMARK_TYPE.TAPE_SHORT_FILEMARKS, 1, true);
2831
}, Throws.Nothing);
2932
}
33+
34+
[Test]
35+
public static void BackupReadTest()
36+
{
37+
using var f = CreateFile(TestCaseSources.LogFile, FileAccess.GENERIC_READ, System.IO.FileShare.Read, null, System.IO.FileMode.Open, FileFlagsAndAttributes.FILE_FLAG_BACKUP_SEMANTICS);
38+
39+
Assert.That(BackupRead(f, true, false, out var streams), ResultIs.Successful);
40+
Assert.That(streams, Is.Not.Empty);
41+
Assert.That(streams, Has.Exactly(1).Matches<(WIN32_STREAM_ID id, SafeAllocatedMemoryHandle? buffer)>(s => s.id.dwStreamId == BACKUP_STREAM_ID.BACKUP_SECURITY_DATA));
42+
Assert.That(streams.All(s => s.buffer is null));
43+
TestContext.WriteLine(string.Join("\n", streams.Select(s => $"{s.id.cStreamName} ({s.id.dwStreamId})")));
44+
45+
Assert.That(BackupRead(f, false, true, out streams), ResultIs.Successful);
46+
Assert.That(streams, Is.Not.Empty);
47+
Assert.That(streams, Has.None.Matches<(WIN32_STREAM_ID id, SafeAllocatedMemoryHandle? buffer)>(s => s.id.dwStreamId == BACKUP_STREAM_ID.BACKUP_SECURITY_DATA));
48+
Assert.That(streams.All(s => s.buffer is not null));
49+
TestContext.WriteLine(string.Join("\n", streams.Select(s => $"{s.id.cStreamName} ({s.id.dwStreamId}) = {s.buffer!.Size}")));
50+
}
3051
}

0 commit comments

Comments
 (0)