From b7aeb3a1051c23382c2c6f676d218066cd66f567 Mon Sep 17 00:00:00 2001 From: Matthew John Cheetham Date: Wed, 3 May 2023 13:49:17 -0700 Subject: [PATCH] platformutils: avoid procstart to get Linux distro info Try using the /etc/os-release file, for systemd distros, in favour of calling out to `uname` which can add extra overhead in the form of process startup. Direct file I/O and parsing should be faster. --- src/shared/Core/PlatformUtils.cs | 75 +++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 11 deletions(-) diff --git a/src/shared/Core/PlatformUtils.cs b/src/shared/Core/PlatformUtils.cs index b04dbe9662..7cca5939e5 100644 --- a/src/shared/Core/PlatformUtils.cs +++ b/src/shared/Core/PlatformUtils.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Linq; using System.Runtime.InteropServices; using GitCredentialManager.Interop.Posix.Native; @@ -349,6 +351,8 @@ private static string GetOSType() return "Unknown"; } + private static string _linuxDistroVersion; + private static string GetOSVersion(ITrace2 trace2) { // @@ -373,22 +377,71 @@ private static string GetOSVersion(ITrace2 trace2) if (IsLinux()) { - var psi = new ProcessStartInfo - { - FileName = "uname", - Arguments = "-a", - RedirectStandardOutput = true - }; + return _linuxDistroVersion ??= GetLinuxDistroVersion(); - using (var uname = new ChildProcess(trace2, psi)) + string GetLinuxDistroVersion() { - uname.Start(Trace2ProcessClass.Other); - uname.Process.WaitForExit(); + // Let's first try to get the distribution information from /etc/os-release + // (or /usr/lib/os-release) which is required in systemd distributions. + // https://www.freedesktop.org/software/systemd/man/os-release.html + foreach (string osReleasePath in new[] { "/etc/os-release", "/usr/lib/os-release" }) + { + if (!File.Exists(osReleasePath)) + { + continue; + } + + // Each line is a key=value pair + char[] split = { '=' }; + IDictionary props = File.ReadAllLines(osReleasePath) + .Select(x => x.Split(split, count: 2)) + .ToDictionary(x => x[0].Trim(), x => x[1].Trim(), StringComparer.OrdinalIgnoreCase); + + // Try to get the PRETTY_NAME first which is a user-friendly description + // including the distro name and version. + if (props.TryGetValue("PRETTY_NAME", out string prettyName)) + { + return prettyName; + } + + // Fall-back to (NAME || ID) + (VERSION || VERSION_ID || VERSION_CODENAME)? + if (props.TryGetValue("NAME", out string distro) || + props.TryGetValue("ID", out distro)) + { + if (props.TryGetValue("VERSION", out string version) || + props.TryGetValue("VERSION_ID", out version) || + props.TryGetValue("VERSION_CODENAME", out version)) + { + return $"{distro} {version}"; + } + + // Return just the distro name if we don't have a version + return distro; + } + } - if (uname.ExitCode == 0) + // If we couldn't get the distribution information from /etc/os-release + // (for example if we're running on a non-systemd distribution), then let's + // use `uname -a` to get at least some information. + var psi = new ProcessStartInfo { - return uname.StandardOutput.ReadToEnd().Trim(); + FileName = "uname", + Arguments = "-a", + RedirectStandardOutput = true + }; + + using (var uname = new ChildProcess(trace2, psi)) + { + uname.Start(Trace2ProcessClass.Other); + uname.Process.WaitForExit(); + + if (uname.ExitCode == 0) + { + return uname.StandardOutput.ReadToEnd().Trim(); + } } + + return "Unknown-Linux"; } }