Skip to content

Commit

Permalink
OSDescription.Linux: return a user-friendly name based on /etc/os-rel…
Browse files Browse the repository at this point in the history
…ease.
  • Loading branch information
tmds committed Mar 27, 2023
1 parent 9b38f2a commit 8451453
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 2 deletions.
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;
}
}
}
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>

0 comments on commit 8451453

Please sign in to comment.