Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;

internal static partial class Interop
{
// Parse information from '/etc/os-release'.
internal static class OSReleaseFile
{
private const string EtcOsReleasePath = "/etc/os-release";

/// <summary>
/// Returns a user-friendly distribution name.
/// </summary>
internal static string? GetPrettyName(string filename = EtcOsReleasePath)
{
if (File.Exists(filename))
{
string[] lines;
try
{
lines = File.ReadAllLines(filename);
}
catch
{
return null;
}

// Parse the NAME, PRETTY_NAME, and VERSION fields.
// These fields are suitable for presentation to the user.
string? prettyName = null, name = null, version = null;
foreach (var line in lines)
{
if (line.StartsWith("PRETTY_NAME=", StringComparison.Ordinal))
{
prettyName = line.Substring("PRETTY_NAME=".Length);
}
else if (line.StartsWith("NAME=", StringComparison.Ordinal))
{
name = line.Substring("NAME=".Length);
}
else if (line.StartsWith("VERSION=", StringComparison.Ordinal))
{
version = line.Substring("VERSION=".Length);
}
}

// Prefer PRETTY_NAME.
if (prettyName is not null)
{
return GetValue(prettyName);
}

// Fall back to: NAME[ VERSION].
if (name is not null)
{
if (version is not null)
{
return $"{GetValue(name)} {GetValue(version)}";
}
return GetValue(name);
}

static string GetValue(string fieldValue)
{
// Remove enclosing quotes.
if ((fieldValue.StartsWith('"') && fieldValue.EndsWith('"')) ||
(fieldValue.StartsWith('\'') && fieldValue.EndsWith('\'')))
{
fieldValue = fieldValue.Substring(1, fieldValue.Length - 2);
}

return fieldValue;
}
}

return null;
Copy link
Member

@stephentoub stephentoub Apr 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I'd find this easier to read if this were moved up to above the static local function.

}
}
}
3 changes: 3 additions & 0 deletions src/libraries/Common/tests/Common.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
Link="Common\Interop\Linux\procfs\Interop.ProcFsStat.cs" />
<Compile Include="$(CommonPath)Interop\Linux\procfs\Interop.ProcFsStat.TryReadStatusFile.cs"
Link="Common\Interop\Linux\Interop.ProcFsStat.TryReadStatusFile.cs" />
<Compile Include="$(CommonPath)Interop\Linux\os-release\Interop.OSReleaseFile.cs"
Link="Common\Interop\Linux\os-release\Interop.OSReleaseFile.cs" />
<Compile Include="$(CommonPath)System\CharArrayHelpers.cs"
Link="Common\System\CharArrayHelpers.cs" />
<Compile Include="$(CommonPath)System\StringExtensions.cs"
Expand Down Expand Up @@ -78,6 +80,7 @@
Link="System\PasteArguments.cs" />
<Compile Include="Tests\Interop\cgroupsTests.cs" />
<Compile Include="Tests\Interop\procfsTests.cs" />
<Compile Include="Tests\Interop\osReleaseTests.cs" />
<Compile Include="Tests\System\IO\PathInternal.Tests.cs" />
<Compile Include="Tests\System\IO\StringParserTests.cs" />
<Compile Include="Tests\System\Net\HttpDateParserTests.cs" />
Expand Down
69 changes: 69 additions & 0 deletions src/libraries/Common/tests/Tests/Interop/osReleaseTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.IO;
using System.Text;
using Xunit;

namespace Common.Tests
{
public class osReleaseTests
{
[Theory]
// Double quotes:
[InlineData("NAME=\"Fedora\"\nVERSION=\"37\"\nPRETTY_NAME=\"Fedora Linux 37\"", "Fedora Linux 37")]
[InlineData("NAME=\"Fedora\"\nVERSION=\"37\"", "Fedora 37")]
[InlineData("NAME=\"Fedora\"", "Fedora")]
// Single quotes:
[InlineData("NAME='Ubuntu'\nVERSION='22.04'\nPRETTY_NAME='Ubuntu Linux 22.04'", "Ubuntu Linux 22.04")]
[InlineData("NAME='Ubuntu'\nVERSION='22.04'", "Ubuntu 22.04")]
[InlineData("NAME='Ubuntu'", "Ubuntu")]
// No quotes:
[InlineData("NAME=Alpine\nVERSION=3.14\nPRETTY_NAME=Alpine_Linux_3.14", "Alpine_Linux_3.14")]
[InlineData("NAME=Alpine\nVERSION=3.14", "Alpine 3.14")]
[InlineData("NAME=Alpine", "Alpine")]
// No pretty name fields:
[InlineData("ID=fedora\nVERSION_ID=37", null)]
[InlineData("", null)]
public static void GetPrettyName_Success(
string content,
string expectedName)
{
string path = Path.GetTempFileName();
try
{
File.WriteAllText(path, content);

string? name = Interop.OSReleaseFile.GetPrettyName(path);
Assert.Equal(expectedName, name);
}
finally { File.Delete(path); }
}

[Fact]
public static void GetPrettyName_NoFile_ReturnsNull()
{
string path = Path.GetRandomFileName();
Assert.False(File.Exists(path));

string? name = Interop.OSReleaseFile.GetPrettyName(path);
Assert.Null(name);
}

[Fact, PlatformSpecific(TestPlatforms.Linux)]
public static void GetPrettyName_CannotRead_ReturnsNull()
{
string path = Path.GetTempFileName();
try
{
File.SetUnixFileMode(path, UnixFileMode.None);
Assert.ThrowsAny<Exception>(() => File.ReadAllText(path));

string? name = Interop.OSReleaseFile.GetPrettyName(path);
Assert.Null(name);
}
finally { File.Delete(path); }
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2379,6 +2379,7 @@
<Compile Include="$(CommonPath)System\IO\StringParser.cs" Condition="'$(TargetsLinux)' == 'true'" Link="Common\System\IO\StringParser.cs" />
<Compile Include="$(CommonPath)Interop\OSX\Interop.libproc.GetProcessInfoById.cs" Condition="'$(TargetsOSX)' == 'true' or '$(TargetsMacCatalyst)' == 'true'" Link="Common\Interop\OSX\Interop.libproc.GetProcessInfoById.cs" />
<Compile Include="$(CommonPath)Interop\SunOS\procfs\Interop.ProcFsStat.TryReadProcessStatusInfo.cs" Condition="'$(Targetsillumos)' == 'true' or '$(TargetsSolaris)' == 'true'" Link="Common\Interop\SunOS\Interop.ProcFsStat.TryReadProcessStatusInfo.cs" />
<Compile Include="$(CommonPath)Interop\Linux\os-release\Interop.OSReleaseFile.cs" Condition="'$(TargetsBrowser)' != 'true' and '$(TargetsWasi)' != 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Unix.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.FreeBSD.cs" Condition="'$(TargetsFreeBSD)' == 'true'" />
<Compile Include="$(MSBuildThisFileDirectory)System\Environment.Linux.cs" Condition="'$(TargetsLinux)' == 'true'" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public static partial class RuntimeInformation
private static string? s_osDescription;
private static volatile int s_osArchPlusOne;

public static string OSDescription => s_osDescription ??= Interop.Sys.GetUnixVersion();
public static string OSDescription => s_osDescription ??= (GetPrettyOSDescription() ?? Interop.Sys.GetUnixVersion());

public static Architecture OSArchitecture
{
Expand All @@ -30,5 +30,15 @@ public static Architecture OSArchitecture
return (Architecture)osArch;
}
}

private static string? GetPrettyOSDescription()
{
if (OperatingSystem.IsLinux())
{
return Interop.OSReleaseFile.GetPrettyName();
}

return null;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,14 @@ public void VerifyWindowsName()
[Fact, PlatformSpecific(TestPlatforms.Linux)] // Checks Linux name in RuntimeInformation
public void VerifyLinuxName()
{
Assert.Contains("linux", RuntimeInformation.OSDescription, StringComparison.OrdinalIgnoreCase);
if (File.Exists("/etc/os/release"))
{
Assert.Equal(Interop.OSReleaseFile.GetPrettyName("/etc/os-release"), RuntimeInformation.OSDescription);
}
else
{
Assert.Contains("linux", RuntimeInformation.OSDescription, StringComparison.OrdinalIgnoreCase);
}
}

[Fact, PlatformSpecific(TestPlatforms.NetBSD)] // Checks NetBSD name in RuntimeInformation
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,7 @@
<Compile Include="DescriptionNameTests.cs" />
<Compile Include="$(CommonPath)Interop\Linux\cgroups\Interop.cgroups.cs"
Link="Common\Interop\Linux\Interop.cgroups.cs" />
<Compile Include="$(CommonPath)Interop\Linux\os-release\Interop.OSReleaseFile.cs"
Link="Interop\Linux\os-release\Interop.OSReleaseFile.cs" />
</ItemGroup>
</Project>