Skip to content

Commit 4cd536e

Browse files
Copilotgfs
andcommitted
Add Windows filesystem metadata (NTFS/FAT/WIM) to disc image extraction
- Add FileAttributes and SecurityDescriptorSddl properties to FileEntryMetadata - Update DiscCommon.TryGetFileMetadata() to handle IDosFileSystem (file attributes) and IWindowsFileSystem (SDDL security descriptors) in addition to IUnixFileSystem - Update DiscCommon.CollectMetadata() to recognize Windows file systems - Add VHDX/NTFS metadata tests verifying FileAttributes and SecurityDescriptorSddl - Restore nuget.config to original configuration Co-authored-by: gfs <98900+gfs@users.noreply.github.com>
1 parent 649794a commit 4cd536e

File tree

5 files changed

+114
-24
lines changed

5 files changed

+114
-24
lines changed

RecursiveExtractor.Tests/ExtractorTests/FileMetadataTests.cs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ public void MetadataDefaults_AreNull()
101101
Assert.Null(metadata.IsExecutable);
102102
Assert.Null(metadata.IsSetUid);
103103
Assert.Null(metadata.IsSetGid);
104+
Assert.Null(metadata.FileAttributes);
105+
Assert.Null(metadata.SecurityDescriptorSddl);
104106
}
105107

106108
[Fact]
@@ -205,4 +207,45 @@ public void IsoRockRidgeEntries_HaveMetadata_Sync()
205207
Assert.NotNull(entry.Metadata.Gid);
206208
}
207209
}
210+
211+
[Fact]
212+
public async Task VhdxNtfsEntries_HaveWindowsMetadata()
213+
{
214+
// TestData.vhdx contains an NTFS file system which implements IDosFileSystem and IWindowsFileSystem
215+
var extractor = new Extractor();
216+
var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", "TestData.vhdx");
217+
var results = await extractor.ExtractAsync(path, new ExtractorOptions() { Recurse = false }).ToListAsync();
218+
219+
Assert.NotEmpty(results);
220+
foreach (var entry in results)
221+
{
222+
Assert.NotNull(entry.Metadata);
223+
// NTFS provides Windows file attributes
224+
Assert.NotNull(entry.Metadata!.FileAttributes);
225+
// NTFS provides security descriptors
226+
Assert.NotNull(entry.Metadata.SecurityDescriptorSddl);
227+
Assert.Contains("D:", entry.Metadata.SecurityDescriptorSddl); // DACL present
228+
// NTFS does not provide Unix metadata
229+
Assert.Null(entry.Metadata.Mode);
230+
Assert.Null(entry.Metadata.Uid);
231+
Assert.Null(entry.Metadata.Gid);
232+
}
233+
}
234+
235+
[Fact]
236+
public void VhdxNtfsEntries_HaveWindowsMetadata_Sync()
237+
{
238+
var extractor = new Extractor();
239+
var path = Path.Combine(Directory.GetCurrentDirectory(), "TestData", "TestDataArchives", "TestData.vhdx");
240+
var results = extractor.Extract(path, new ExtractorOptions() { Recurse = false }).ToList();
241+
242+
Assert.NotEmpty(results);
243+
foreach (var entry in results)
244+
{
245+
Assert.NotNull(entry.Metadata);
246+
Assert.NotNull(entry.Metadata!.FileAttributes);
247+
Assert.NotNull(entry.Metadata.SecurityDescriptorSddl);
248+
Assert.Null(entry.Metadata.Mode);
249+
}
250+
}
208251
}

RecursiveExtractor/Extractors/DiscCommon.cs

Lines changed: 52 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,71 @@ public static class DiscCommon
1717
/// <summary>
1818
/// Tries to extract file metadata from a DiscUtils file system entry.
1919
/// For file systems implementing <see cref="IUnixFileSystem"/> (Ext, Xfs, Btrfs, HfsPlus),
20-
/// returns permissions, UID, and GID. Returns null for unsupported file systems.
20+
/// returns permissions, UID, and GID.
21+
/// For file systems implementing <see cref="IDosFileSystem"/> (NTFS, FAT, WIM),
22+
/// returns Windows file attributes.
23+
/// For file systems implementing <see cref="IWindowsFileSystem"/> (NTFS, WIM),
24+
/// also returns the security descriptor in SDDL format.
25+
/// Returns null for file systems that support none of these interfaces.
2126
/// </summary>
2227
/// <param name="fs">The opened disc file system</param>
2328
/// <param name="filePath">Path of the file within the file system</param>
2429
/// <returns>Populated <see cref="FileEntryMetadata"/> or null when not available</returns>
2530
internal static FileEntryMetadata? TryGetFileMetadata(DiscFileSystem fs, string filePath)
2631
{
27-
if (fs is not IUnixFileSystem unixFs)
32+
FileEntryMetadata? metadata = null;
33+
34+
if (fs is IUnixFileSystem unixFs)
2835
{
29-
return null;
36+
try
37+
{
38+
var info = unixFs.GetUnixFileInfo(filePath);
39+
metadata = new FileEntryMetadata
40+
{
41+
Mode = (long)info.Permissions,
42+
Uid = info.UserId,
43+
Gid = info.GroupId
44+
};
45+
}
46+
catch (Exception e)
47+
{
48+
Logger.Debug(e, "Could not retrieve Unix metadata for {0}", filePath);
49+
}
3050
}
3151

32-
try
52+
if (fs is IDosFileSystem dosFs)
3353
{
34-
var info = unixFs.GetUnixFileInfo(filePath);
35-
return new FileEntryMetadata
54+
try
3655
{
37-
Mode = (long)info.Permissions,
38-
Uid = info.UserId,
39-
Gid = info.GroupId
40-
};
56+
var winInfo = dosFs.GetFileStandardInformation(filePath);
57+
metadata ??= new FileEntryMetadata();
58+
metadata.FileAttributes = winInfo.FileAttributes;
59+
}
60+
catch (Exception e)
61+
{
62+
Logger.Debug(e, "Could not retrieve DOS file attributes for {0}", filePath);
63+
}
4164
}
42-
catch (Exception e)
65+
66+
if (fs is IWindowsFileSystem windowsFs)
4367
{
44-
Logger.Debug(e, "Could not retrieve Unix metadata for {0}", filePath);
45-
return null;
68+
try
69+
{
70+
var securityDescriptor = windowsFs.GetSecurity(filePath);
71+
if (securityDescriptor != null)
72+
{
73+
metadata ??= new FileEntryMetadata();
74+
metadata.SecurityDescriptorSddl = securityDescriptor.GetSddlForm(
75+
DiscUtils.Core.WindowsSecurity.AccessControl.AccessControlSections.All);
76+
}
77+
}
78+
catch (Exception e)
79+
{
80+
Logger.Debug(e, "Could not retrieve security descriptor for {0}", filePath);
81+
}
4682
}
83+
84+
return metadata;
4785
}
4886

4987
/// <summary>
@@ -55,7 +93,7 @@ public static class DiscCommon
5593
/// <returns>A dictionary mapping file paths to metadata, or null if the file system does not support metadata</returns>
5694
internal static Dictionary<string, FileEntryMetadata>? CollectMetadata(DiscFileSystem fs, DiscFileInfo[] fileInfos)
5795
{
58-
if (fs is not IUnixFileSystem)
96+
if (fs is not IUnixFileSystem && fs is not IDosFileSystem && fs is not IWindowsFileSystem)
5997
{
6098
return null;
6199
}

RecursiveExtractor/FileEntryMetadata.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
// Copyright (c) Microsoft Corporation. Licensed under the MIT License.
22

3+
using System.IO;
4+
35
namespace Microsoft.CST.RecursiveExtractor
46
{
57
/// <summary>
@@ -43,5 +45,19 @@ public class FileEntryMetadata
4345
/// Null if not available from the archive format.
4446
/// </summary>
4547
public long? Gid { get; set; }
48+
49+
/// <summary>
50+
/// The Windows file attributes (e.g., ReadOnly, Hidden, System, Archive).
51+
/// Available for NTFS, FAT, and WIM file systems.
52+
/// Null if not available from the archive format.
53+
/// </summary>
54+
public FileAttributes? FileAttributes { get; set; }
55+
56+
/// <summary>
57+
/// The NTFS security descriptor in SDDL (Security Descriptor Definition Language) format.
58+
/// Available for NTFS and WIM file systems that implement <c>IWindowsFileSystem</c>.
59+
/// Null if not available from the archive format.
60+
/// </summary>
61+
public string? SecurityDescriptorSddl { get; set; }
4662
}
4763
}

nuget.config

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
<?xml version="1.0" encoding="utf-8"?>
1+
<?xml version="1.0" encoding="utf-8"?>
22
<configuration>
33
<packageSources>
44
<clear />
5-
<add key="PublicRegistriesFeed" value="https://api.nuget.org/v3/index.json" />
5+
<add key="PublicRegistriesFeed" value="https://pkgs.dev.azure.com/microsoft-sdl/General/_packaging/PublicRegistriesFeed/nuget/v3/index.json" />
66
</packageSources>
7-
</configuration>
7+
</configuration>

nuget.config.original

Lines changed: 0 additions & 7 deletions
This file was deleted.

0 commit comments

Comments
 (0)