// Copyright (C) 2024, The Duplicati Team // https://duplicati.com, hello@duplicati.com // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. using System; using System.Collections.Generic; using System.Linq; using Duplicati.Library.Common; namespace Duplicati.Library.UsageReporter { /// /// Helper class for extracting operating system info /// public static class OSInfoHelper { /// /// Runs a commandline program and returns stdout as a string. /// /// The stdout value. /// The command to run. /// The commandline arguments. private static string RunProgramAndReadOutput(string cmd, string args) { System.Diagnostics.Process pi = null; try { var psi = new System.Diagnostics.ProcessStartInfo(cmd, args) { RedirectStandardOutput = true, RedirectStandardError = true, // Suppress error messages RedirectStandardInput = false, UseShellExecute = false }; pi = System.Diagnostics.Process.Start(psi); pi.WaitForExit(5000); if (pi.HasExited) return pi.StandardOutput.ReadToEnd().Trim(); } catch { } finally { if (pi != null && !pi.HasExited) try { pi.Kill(); } catch { } } return null; } /// /// Gets a single string, identifying the OS of the current platform /// The output should not contain any machine identifiers, only OS info /// /// The platform. public static string PlatformString { get { if (!(OperatingSystem.IsMacOS() || OperatingSystem.IsLinux())) { return Environment.OSVersion.ToString(); } else if (OperatingSystem.IsMacOS()) { var m = RunProgramAndReadOutput("sw_vers", null); if (m != null) { var lines = m.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); var product = lines.Where(x => x.Trim().StartsWith("ProductName:", StringComparison.Ordinal)).Select(x => x.Trim().Substring("ProductName:".Length).Trim()).FirstOrDefault(); var version = lines.Where(x => x.Trim().StartsWith("ProductVersion:", StringComparison.Ordinal)).Select(x => x.Trim().Substring("ProductVersion:".Length).Trim()).FirstOrDefault(); var build = lines.Where(x => x.Trim().StartsWith("BuildVersion:", StringComparison.Ordinal)).Select(x => x.Trim().Substring("BuildVersion:".Length).Trim()).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(product)) return string.Format("{0} {1} {2}", product, version, build); } return RunProgramAndReadOutput("uname", "-srvmp"); } else { // Manual extraction, this grabs the distro identification files directly try { var keys = new List>(); foreach (var fn in System.IO.Directory.GetFiles("/etc/", "*-release")) { var fi = new System.IO.FileInfo(fn); if (fi.Exists && fi.Length < 1024 * 10) { try { keys.AddRange( System.IO.File.ReadAllLines(fi.FullName) .Select(x => { var items = (x ?? string.Empty).Split(new char[] { '=' }, 2).Select(y => (y ?? string.Empty).Trim('"')).ToArray(); if (items.Length != 2 || items.Any(y => string.IsNullOrWhiteSpace(y))) return null; return new Tuple(items[0], items[1]); }) .Where(x => x != null) ); } catch { } } } var primary = keys.FirstOrDefault(x => string.Equals(x.Item1, "PRETTY_NAME", StringComparison.OrdinalIgnoreCase)); if (primary != null) return primary.Item2; var name = keys.FirstOrDefault(x => string.Equals(x.Item1, "NAME", StringComparison.OrdinalIgnoreCase)); var version = keys.FirstOrDefault(x => string.Equals(x.Item1, "VERSION", StringComparison.OrdinalIgnoreCase)); name = name ?? keys.FirstOrDefault(x => string.Equals(x.Item1, "DISTRIB_ID", StringComparison.OrdinalIgnoreCase)); name = name ?? keys.FirstOrDefault(x => string.Equals(x.Item1, "ID", StringComparison.OrdinalIgnoreCase)); version = version ?? keys.FirstOrDefault(x => string.Equals(x.Item1, "DISTRIB_RELEASE", StringComparison.OrdinalIgnoreCase)); version = version ?? keys.FirstOrDefault(x => string.Equals(x.Item1, "VERSION_ID", StringComparison.OrdinalIgnoreCase)); if (name != null && version != null) return string.Format("{0} {1}", name.Item2, version.Item2); if (name != null) return name.Item2; } catch { } // This works on debian based distros, but emits a warning to stderr var m = RunProgramAndReadOutput("lsb_release", "-a"); if (m != null) { var lines = m.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); var line = lines.Where(x => x.Trim().StartsWith("Description:", StringComparison.Ordinal)).Select(x => x.Trim().Substring("Description:".Length).Trim()).FirstOrDefault(); if (!string.IsNullOrWhiteSpace(line)) return line; } // This should work on all Linux based systems return RunProgramAndReadOutput("uname", "-srvmpio"); } } } } }