Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Library/DiscUtils.Lvm/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("LibraryTests")]
68 changes: 59 additions & 9 deletions Library/DiscUtils.Lvm/MetadataSegmentSection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,13 +51,15 @@ internal void Parse(string head, TextReader data)
if (line.AsSpan().Contains("=".AsSpan(), StringComparison.Ordinal))
{
var parameter = Metadata.ParseParameter(line.AsMemory());
var parameterValue = parameter.Value.Span;

switch (parameter.Key.ToString().ToLowerInvariant())
{
case "start_extent":
StartExtent = Metadata.ParseNumericValue(parameter.Value.Span);
StartExtent = Metadata.ParseNumericValue(parameterValue);
break;
case "extent_count":
ExtentCount = Metadata.ParseNumericValue(parameter.Value.Span);
ExtentCount = Metadata.ParseNumericValue(parameterValue);
break;
case "type":
var value = Metadata.ParseStringValue(parameter.Value.Span);
Expand Down Expand Up @@ -127,12 +129,23 @@ internal void Parse(string head, TextReader data)

break;
case "stripe_count":
StripeCount = Metadata.ParseNumericValue(parameter.Value.Span);
StripeCount = Metadata.ParseNumericValue(parameterValue);
break;
case "stripes":
if (parameter.Value.Span.Equals("[".AsSpan(), StringComparison.Ordinal))
if (parameterValue.Equals("[", StringComparison.Ordinal))
{
// Multi-line section
Stripes = ParseMultiLineStripesSection(data).ToArray();
}
else if (parameterValue.StartsWith("[", StringComparison.Ordinal) && parameterValue.EndsWith("]", StringComparison.Ordinal))
{
// Single line section
// Exclude the brackets from the input
Stripes = ParseSinglelineStripesSection(parameterValue[1..^1]).ToArray();
}
else
{
Stripes = ParseStripesSection(data).ToArray();
throw new ArgumentException("Unsupported or invalid stripe format", line);
}

break;
Expand All @@ -151,7 +164,7 @@ internal void Parse(string head, TextReader data)
}
}

private static IEnumerable<MetadataStripe> ParseStripesSection(TextReader data)
private static IEnumerable<MetadataStripe> ParseMultiLineStripesSection(TextReader data)
{
string line;
while ((line = Metadata.ReadLine(data)) != null)
Expand All @@ -166,11 +179,48 @@ private static IEnumerable<MetadataStripe> ParseStripesSection(TextReader data)
yield break;
}

var pv = new MetadataStripe();
pv.Parse(line);
yield return pv;
var metadataStripes = ParseSinglelineStripesSection(line);

foreach (var metadataStripe in metadataStripes)
{
yield return metadataStripe;
}
}
}

private static List<MetadataStripe> ParseSinglelineStripesSection(ReadOnlySpan<char> data)
{
var metadataStripes = new List<MetadataStripe>();
while (!data.IsEmpty)
{
// Find the first comma separating the pair
var firstCommaIndex = data.IndexOf(',');
if (firstCommaIndex == -1) break;

// Extract the first value
var volumeNameSpan = data.Slice(0, firstCommaIndex);
data = data[(firstCommaIndex + 1)..];

// Find the second comma separating the pair
var secondCommaIndex = data.IndexOf(',');
var extentNumberSpan = secondCommaIndex == -1
? data // Last value
: data.Slice(0, secondCommaIndex);

// Create and parse the MetadataStripe
var metadataStripe = new MetadataStripe();
metadataStripe.Parse(volumeNameSpan, extentNumberSpan);

metadataStripes.Add(metadataStripe);

// Move to the next pair
data = secondCommaIndex == -1
? ReadOnlySpan<char>.Empty
: data[(secondCommaIndex + 1)..];
}

return metadataStripes;
}
}

[Flags]
Expand Down
12 changes: 3 additions & 9 deletions Library/DiscUtils.Lvm/MetadataStripe.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,9 @@ internal class MetadataStripe
public string PhysicalVolumeName;
public ulong StartExtentNumber;

internal void Parse(string line)
internal void Parse(ReadOnlySpan<char> volumeNameSpan, ReadOnlySpan<char> extentNumberSpan)
{
var parts = line.Split(',');
if (parts.Length != 2)
{
throw new ArgumentException("invalid stripe format", line);
}

PhysicalVolumeName = Metadata.ParseStringValue(parts[0].AsSpan());
StartExtentNumber = Metadata.ParseNumericValue(parts[1].AsSpan());
PhysicalVolumeName = Metadata.ParseStringValue(volumeNameSpan);
StartExtentNumber = Metadata.ParseNumericValue(extentNumberSpan);
}
}
172 changes: 172 additions & 0 deletions Tests/LibraryTests/Lvm/MetadataSegmentSectionTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
using System;
using System.Collections.Generic;
using System.IO;
using DiscUtils.Lvm;
using Xunit;

namespace LibraryTests.Lvm;

public class MetadataSegmentSectionTests
{
public static IEnumerable<object[]> MetadataTestCases =>
[
[
"segment1 {",
"""
start_extent = 0
extent_count = 2560
type = "striped"
stripe_count = 1
stripes = ["pv0", 3840]
}
""",
new MetadataSegmentSection
{
Name = "segment1",
StartExtent = 0,
ExtentCount = 2560,
Type = SegmentType.Striped,
StripeCount = 1,
Stripes =
[
new MetadataStripe
{
PhysicalVolumeName = "pv0",
StartExtentNumber = 3840,
},
],
},
],
[
"segment2 {",
"""
start_extent = 0
extent_count = 94
type = "striped"
stripe_count = 1
stripes = [
"pv0", 1813
]
}
""",
new MetadataSegmentSection
{
Name = "segment2",
StartExtent = 0,
ExtentCount = 94,
Type = SegmentType.Striped,
StripeCount = 1,
Stripes =
[
new MetadataStripe
{
PhysicalVolumeName = "pv0",
StartExtentNumber = 1813,
},
],
},
],
[
"segment3 {",
"""
start_extent = 0
extent_count = 768
type = "striped"
stripe_count = 2
stripes = ["pv0", 512, "pv1", 0]
}
""",
new MetadataSegmentSection
{
Name = "segment3",
StartExtent = 0,
ExtentCount = 768,
StripeCount = 2,
Type = SegmentType.Striped,
Stripes =
[
new MetadataStripe
{
PhysicalVolumeName = "pv0",
StartExtentNumber = 512,
},
new MetadataStripe
{
PhysicalVolumeName = "pv1",
StartExtentNumber = 0,
},
],
},
],
[
"segment4 {",
"""
start_extent = 0
extent_count = 1024
type = "striped"
stripe_count = 3
stripes = [
"pv0", 1280, "pv1", 768,
"pv2", 0
]
}
""",
new MetadataSegmentSection
{
Name = "segment4",
StartExtent = 0,
ExtentCount = 1024,
Type = SegmentType.Striped,
StripeCount = 3,
Stripes =
[
new MetadataStripe
{
PhysicalVolumeName = "pv0",
StartExtentNumber = 1280,
},
new MetadataStripe
{
PhysicalVolumeName = "pv1",
StartExtentNumber = 768,
},
new MetadataStripe
{
PhysicalVolumeName = "pv2",
StartExtentNumber = 0,
},
],
},
],
];

[Theory()]
[MemberData(nameof(MetadataTestCases))]
internal void ParseShouldSucceedForStripedSegment(string head, string dataString, MetadataSegmentSection expectedMetadataSegmentSection)
{
var metadataSegmentSection = new MetadataSegmentSection();
metadataSegmentSection.Parse(head, new StringReader(dataString));

Assert.Equivalent(expectedMetadataSegmentSection, metadataSegmentSection);
}

[Fact]
public void ParseShouldThrowForInvalidStripedSegment()
{
var textReader = new StringReader(
"""
start_extent = 0
extent_count = 2560
type = "striped"
stripe_count = 1
stripes = ["pv0", 3840
]
}
""");

var metadataSegmentSection = new MetadataSegmentSection();

var actualException = Assert.Throws<ArgumentException>(() => metadataSegmentSection.Parse("segment1 {", textReader));
Assert.StartsWith("Unsupported or invalid stripe format", actualException.Message);
}
}