blob: f71876d7bf27a47e16277e52f091d38a26a4e0e5 [file]
#region Apache License
//
// Licensed to the Apache Software Foundation (ASF) under one or more
// contributor license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright ownership.
// The ASF licenses this file to you under the Apache License, Version 2.0
// (the "License"); you may not use this file except in compliance with
// the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
#endregion
using System;
using System.Configuration;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.IO;
using System.Collections;
using System.Runtime.CompilerServices;
namespace log4net.Util;
/// <summary>
/// Utility class for system specific information.
/// </summary>
/// <author>Nicko Cadell</author>
/// <author>Gert Driesen</author>
/// <author>Alexey Solofnenko</author>
public static class SystemInfo
{
private const string DefaultNullText = "(null)";
private const string DefaultNotAvailableText = "NOT AVAILABLE";
/// <summary>
/// Is OperatingSystem Android
/// </summary>
internal static bool IsAndroid { get; } = IsAndroidCore();
/// <summary>
/// Is the mono runtime used
/// </summary>
internal static bool IsMono { get; } = Type.GetType("Mono.Runtime") is not null;
/// <summary>
/// Initialize default values for private static fields.
/// </summary>
/// <remarks>
/// <para>
/// Only static methods are exposed from this type.
/// </para>
/// </remarks>
static SystemInfo()
{
string nullText = DefaultNullText;
string notAvailableText = DefaultNotAvailableText;
// Look for log4net.NullText in AppSettings
string? nullTextAppSettingsKey = GetAppSetting("log4net.NullText");
if (!string.IsNullOrEmpty(nullTextAppSettingsKey))
{
LogLog.Debug(_declaringType, $"Initializing NullText value to [{nullTextAppSettingsKey}].");
nullText = nullTextAppSettingsKey!;
}
// Look for log4net.NotAvailableText in AppSettings
string? notAvailableTextAppSettingsKey = GetAppSetting("log4net.NotAvailableText");
if (!string.IsNullOrEmpty(notAvailableTextAppSettingsKey))
{
LogLog.Debug(_declaringType, $"Initializing NotAvailableText value to [{notAvailableTextAppSettingsKey}].");
notAvailableText = notAvailableTextAppSettingsKey!;
}
NotAvailableText = notAvailableText;
NullText = nullText;
}
private static bool IsAndroidCore() // https://stackoverflow.com/questions/47521008/how-can-i-distinguish-between-unix-and-android-on-netstandard-2-0
{
if (Environment.OSVersion.Platform != PlatformID.Unix)
return false;
using System.Diagnostics.Process process = new()
{
StartInfo = new()
{
FileName = "getprop",
Arguments = "ro.build.user",
RedirectStandardOutput = true,
UseShellExecute = false,
CreateNoWindow = true
}
};
try
{
process.Start();
string output = process.StandardOutput.ReadToEnd();
return !string.IsNullOrEmpty(output);
}
catch (Exception ex) when (!ex.IsFatal())
{
return false;
}
}
/// <summary>
/// Gets the system dependent line terminator.
/// </summary>
public static string NewLine => Environment.NewLine;
/// <summary>
/// Gets the base directory for this <see cref="AppDomain"/>.
/// </summary>
/// <remarks>
/// <para>
/// The value returned may be either a local file path or a URI.
/// </para>
/// </remarks>
public static string ApplicationBaseDirectory => AppDomain.CurrentDomain.BaseDirectory;
/// <summary>
/// Gets the path to the configuration file for the current <see cref="AppDomain"/>.
/// </summary>
/// <remarks>
/// <para>
/// The value returned may be either a local file path or a URI.
/// </para>
/// </remarks>
public static string ConfigurationFileLocation
{
get
{
#if NET462_OR_GREATER
return AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
#else
return EntryAssemblyLocation + ".config";
#endif
}
}
private static string? _entryAssemblyLocation;
/// <summary>
/// Gets the path to the file that first executed in the current <see cref="AppDomain"/>.
/// </summary>
public static string EntryAssemblyLocation
{
get
{
if (_entryAssemblyLocation is not null)
{
return _entryAssemblyLocation;
}
Assembly entryAssembly = Assembly.GetEntryAssembly()
?? throw new InvalidOperationException($"Unable to determine EntryAssembly location: EntryAssembly is null. Try explicitly setting {nameof(SystemInfo)}.{nameof(EntryAssemblyLocation)}");
string location = GetAssemblyLocation(entryAssembly);
if (location.Length == 0)
{
// A single file application has no file on disk for the assemblies it embeds, so the
// closest thing to the entry assembly's path is the directory it was published to.
location = Path.Combine(AppContext.BaseDirectory, $"{entryAssembly.GetName().Name}.dll");
}
return _entryAssemblyLocation = location;
}
set => _entryAssemblyLocation = value;
}
/// <summary>
/// Reads <see cref="Assembly.Location"/>.
/// </summary>
/// <param name="assembly">the assembly to locate</param>
/// <returns>the path to the assembly, or an empty string when it has none</returns>
/// <remarks>
/// <para>
/// An assembly embedded in a single file application has no path, and
/// <see cref="Assembly.Location"/> returns an empty string for it rather than failing. Callers
/// handle that here, which is what the suppressed warning asks them to do.
/// </para>
/// </remarks>
[UnconditionalSuppressMessage("SingleFile", "IL3000",
Justification = "The empty string returned by a single file application is handled by the callers.")]
private static string GetAssemblyLocation(Assembly assembly) => assembly.Location;
/// <summary>
/// Gets the ID of the current thread.
/// </summary>
public static int CurrentThreadId => Environment.CurrentManagedThreadId;
/// <summary>
/// Gets the host name or machine name for the current machine.
/// </summary>
/// <remarks>
/// <para>
/// The host name (<see cref="System.Net.Dns.GetHostName"/>) or
/// the machine name (<see cref="Environment.MachineName"/>) for
/// the current machine, or if neither of these are available
/// then <c>NOT AVAILABLE</c> is returned.
/// </para>
/// </remarks>
public static string HostName
{
get
{
if (_sHostName is null)
{
// Get the DNS host name of the current machine
try
{
// Lookup the host name
_sHostName = System.Net.Dns.GetHostName();
}
catch (System.Net.Sockets.SocketException)
{
LogLog.Debug(_declaringType, "Socket exception occurred while getting the dns hostname. Error Ignored.");
}
catch (System.Security.SecurityException)
{
// We may get a security exception looking up the hostname
// You must have Unrestricted DnsPermission to access resource
LogLog.Debug(_declaringType, "Security exception occurred while getting the dns hostname. Error Ignored.");
}
catch (Exception e) when (!e.IsFatal())
{
LogLog.Debug(_declaringType, "Some other exception occurred while getting the dns hostname. Error Ignored.", e);
}
// Get the NETBIOS machine name of the current machine
if (string.IsNullOrEmpty(_sHostName))
{
try
{
_sHostName = Environment.MachineName;
}
catch (InvalidOperationException)
{
}
catch (System.Security.SecurityException)
{
// We may get a security exception looking up the machine name
// You must have Unrestricted EnvironmentPermission to access resource
}
}
// Couldn't find a value
if (string.IsNullOrEmpty(_sHostName))
{
_sHostName = NotAvailableText;
LogLog.Debug(_declaringType, "Could not determine the hostname. Error Ignored. Empty host name will be used");
}
}
return _sHostName!;
}
}
/// <summary>
/// Gets this application's friendly name.
/// </summary>
/// <remarks>
/// <para>
/// If available the name of the application is retrieved from
/// the <c>AppDomain</c> using <c>AppDomain.CurrentDomain.FriendlyName</c>.
/// </para>
/// <para>
/// Otherwise the file name of the entry assembly is used.
/// </para>
/// </remarks>
public static string ApplicationFriendlyName
{
get
{
if (_sAppFriendlyName is null)
{
try
{
_sAppFriendlyName = AppDomain.CurrentDomain.FriendlyName;
}
catch (System.Security.SecurityException)
{
// This security exception will occur if the caller does not have
// some undefined set of SecurityPermission flags.
LogLog.Debug(_declaringType, "Security exception while trying to get current domain friendly name. Error Ignored.");
}
if (string.IsNullOrEmpty(_sAppFriendlyName))
{
try
{
string assemblyLocation = EntryAssemblyLocation;
_sAppFriendlyName = Path.GetFileName(assemblyLocation);
}
catch (System.Security.SecurityException)
{
// Caller needs path discovery permission
}
}
if (string.IsNullOrEmpty(_sAppFriendlyName))
{
_sAppFriendlyName = NotAvailableText;
}
}
return _sAppFriendlyName!;
}
}
/// <summary>
/// Get the UTC start time for the current process.
/// </summary>
/// <remarks>
/// <para>
/// This is the UTC time at which the log4net library was loaded into the
/// AppDomain. Due to reports of a hang in the call to <c>System.Diagnostics.Process.StartTime</c>
/// this is not the start time for the current process.
/// </para>
/// <para>
/// The log4net library should be loaded by an application early during its
/// startup, therefore this start time should be a good approximation for
/// the actual start time.
/// </para>
/// <para>
/// Note that AppDomains may be loaded and unloaded within the
/// same process without the process terminating, however this start time
/// will be set per AppDomain.
/// </para>
/// </remarks>
public static DateTime ProcessStartTimeUtc { get; } = DateTime.UtcNow;
/// <summary>
/// Text to output when a <see langword="null"/> is encountered.
/// </summary>
/// <remarks>
/// <para>
/// Use this value to indicate a <see langword="null"/> has been encountered while
/// outputting a string representation of an item.
/// </para>
/// <para>
/// The default value is <c>(null)</c>. This value can be overridden by specifying
/// a value for the <c>log4net.NullText</c> appSetting in the application's
/// .config file.
/// </para>
/// </remarks>
public static string NullText { get; set; }
/// <summary>
/// Text to output when an unsupported feature is requested.
/// </summary>
/// <remarks>
/// <para>
/// Use this value when an unsupported feature is requested.
/// </para>
/// <para>
/// The default value is <c>NOT AVAILABLE</c>. This value can be overridden by specifying
/// a value for the <c>log4net.NotAvailableText</c> appSetting in the application's
/// .config file.
/// </para>
/// </remarks>
public static string NotAvailableText { get; set; }
/// <summary>
/// Gets the assembly location path for the specified assembly.
/// </summary>
/// <param name="myAssembly">The assembly to get the location for.</param>
/// <returns>The location of the assembly.</returns>
/// <remarks>
/// <para>
/// This method does not guarantee to return the correct path
/// to the assembly. If only tries to give an indication as to
/// where the assembly was loaded from.
/// </para>
/// </remarks>
public static string AssemblyLocationInfo(Assembly myAssembly)
{
if (myAssembly.EnsureNotNull().GlobalAssemblyCache)
{
return "Global Assembly Cache";
}
try
{
if (myAssembly.IsDynamic)
{
return "Dynamic Assembly";
}
if (myAssembly.GetType().FullName == "System.Reflection.Emit.InternalAssemblyBuilder")
{
return "Dynamic Assembly";
}
// This call requires FileIOPermission for access to the path
// if we don't have permission then we just ignore it and
// carry on.
string location = GetAssemblyLocation(myAssembly);
return location.Length > 0 ? location : "Single File Application";
}
catch (NotSupportedException)
{
// The location information may be unavailable for dynamic assemblies and a NotSupportedException
// is thrown in those cases. See: http://msdn.microsoft.com/de-de/library/system.reflection.assembly.location.aspx
return "Dynamic Assembly";
}
catch (TargetInvocationException ex)
{
return $"Location Detect Failed ({ex.Message})";
}
catch (ArgumentException ex)
{
return $"Location Detect Failed ({ex.Message})";
}
catch (System.Security.SecurityException)
{
return "Location Permission Denied";
}
}
/// <summary>
/// Gets the short name of the <see cref="Assembly" />.
/// </summary>
/// <param name="myAssembly">The <see cref="Assembly" /> to get the name for.</param>
/// <returns>The short name of the <see cref="Assembly" />.</returns>
/// <remarks>
/// <para>
/// The short name of the assembly is the <see cref="Assembly.FullName" />
/// without the version, culture, or public key. i.e. it is just the
/// assembly's file name without the extension.
/// </para>
/// <para>
/// Because of a FileIOPermission security demand we cannot do
/// the obvious Assembly.GetName().Name. We are allowed to get
/// the <see cref="Assembly.FullName" /> of the assembly so we
/// start from there and strip out just the assembly name.
/// </para>
/// </remarks>
public static string AssemblyShortName(Assembly myAssembly)
{
string name = myAssembly.EnsureNotNull().FullName ?? string.Empty;
int offset = name.IndexOf(',');
if (offset > 0)
{
name = name.Substring(0, offset);
}
return name.Trim();
}
/// <summary>
/// Gets the file name portion of the <see cref="Assembly" />, including the extension.
/// </summary>
/// <param name="myAssembly">The <see cref="Assembly" /> to get the file name for.</param>
/// <returns>The file name of the assembly.</returns>
/// <remarks>
/// <para>
/// Gets the file name portion of the <see cref="Assembly" />, including the extension.
/// </para>
/// </remarks>
public static string AssemblyFileName(Assembly myAssembly)
=> Path.GetFileName(myAssembly.EnsureNotNull().Location);
/// <summary>
/// Loads the type specified in the type string.
/// </summary>
/// <param name="relativeType">A sibling type to use to load the type.</param>
/// <param name="typeName">The name of the type to load.</param>
/// <param name="throwOnError">Flag set to <see langword="true"/> to throw an exception if the type cannot be loaded.</param>
/// <param name="ignoreCase"><see langword="true"/> to ignore the case of the type name; otherwise, <see langword="false"/></param>
/// <returns>The type loaded or <see langword="null"/> if it could not be loaded.</returns>
/// <remarks>
/// <para>
/// If the type name is fully qualified, i.e. if contains an assembly name in
/// the type name, the type will be loaded from the system using
/// <see cref="Type.GetType(string,bool)"/>.
/// </para>
/// <para>
/// If the type name is not fully qualified, it will be loaded from the assembly
/// containing the specified relative type. If the type is not found in the assembly
/// then all the loaded assemblies will be searched for the type.
/// </para>
/// </remarks>
public static Type? GetTypeFromString(Type relativeType, string typeName, bool throwOnError, bool ignoreCase)
=> GetTypeFromString(relativeType.EnsureNotNull().Assembly, typeName, throwOnError, ignoreCase);
/// <summary>
/// Loads the type specified in the type string.
/// </summary>
/// <param name="typeName">The name of the type to load.</param>
/// <param name="throwOnError">Flag set to <see langword="true"/> to throw an exception if the type cannot be loaded.</param>
/// <param name="ignoreCase"><see langword="true"/> to ignore the case of the type name; otherwise, <see langword="false"/></param>
/// <returns>The type loaded or <see langword="null"/> if it could not be loaded.</returns>
/// <remarks>
/// <para>
/// If the type name is fully qualified, i.e. if contains an assembly name in
/// the type name, the type will be loaded from the system using
/// <see cref="Type.GetType(string,bool)"/>.
/// </para>
/// <para>
/// If the type name is not fully qualified it will be loaded from the
/// assembly that is directly calling this method. If the type is not found
/// in the assembly then all the loaded assemblies will be searched for the type.
/// </para>
/// </remarks>
public static Type? GetTypeFromString(string typeName, bool throwOnError, bool ignoreCase)
=> GetTypeFromString(CallerAssembly.IsSupported
? Assembly.GetCallingAssembly()
: CallerAssembly.Fallback, typeName, throwOnError, ignoreCase);
/// <summary>
/// Loads the type specified in the type string.
/// </summary>
/// <param name="relativeAssembly">An assembly to load the type from.</param>
/// <param name="typeName">The name of the type to load.</param>
/// <param name="throwOnError">Flag set to <see langword="true"/> to throw an exception if the type cannot be loaded.</param>
/// <param name="ignoreCase"><see langword="true"/> to ignore the case of the type name; otherwise, <see langword="false"/></param>
/// <returns>The type loaded or <see langword="null"/> if it could not be loaded.</returns>
/// <remarks>
/// <para>
/// If the type name is fully qualified, i.e. if contains an assembly name in
/// the type name, the type will be loaded from the system using
/// <see cref="Type.GetType(string,bool)"/>.
/// </para>
/// <para>
/// If the type name is not fully qualified it will be loaded from the specified
/// assembly. If the type is not found in the assembly then all the loaded assemblies
/// will be searched for the type.
/// </para>
/// </remarks>
public static Type? GetTypeFromString(Assembly relativeAssembly, string typeName, bool throwOnError, bool ignoreCase)
{
// Check if the type name specifies the assembly name
if (typeName.EnsureNotNull().IndexOf(',') == -1)
{
// Attempt to look up the type from the relativeAssembly
if (relativeAssembly.EnsureNotNull().GetType(typeName, false, ignoreCase) is Type type)
{
return type;
}
Assembly[]? loadedAssemblies = null;
try
{
loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies();
}
catch (System.Security.SecurityException)
{
// Insufficient permissions to get the list of loaded assemblies
}
if (loadedAssemblies is not null)
{
Type? fallback = null;
// Search the loaded assemblies for the type
foreach (Assembly assembly in loadedAssemblies)
{
if (assembly.GetType(typeName, false, ignoreCase) is Type t)
{
// Found type in loaded assembly
LogLog.Debug(_declaringType, $"Loaded type [{typeName}] from assembly [{assembly.FullName}] by searching loaded assemblies.");
if (assembly.GlobalAssemblyCache)
{
fallback = t;
}
else
{
return t;
}
}
}
if (fallback is not null)
{
return fallback;
}
}
// Didn't find the type
if (throwOnError)
{
throw new TypeLoadException($"Could not load type [{typeName}]. Tried assembly [{relativeAssembly.FullName}] and all loaded assemblies");
}
return null;
}
// Includes explicit assembly name
return Type.GetType(typeName, throwOnError, ignoreCase);
}
/// <summary>
/// Creates an <see cref="ArgumentOutOfRangeException"/>
/// </summary>
/// <param name="parameterName">The name of the parameter that caused the exception</param>
/// <param name="actualValue">The value of the argument that causes this exception</param>
/// <param name="message">The message that describes the error</param>
/// <returns>
/// A new instance of the <see cref="ArgumentOutOfRangeException"/> class
/// with the specified error message, parameter name, and value
/// of the argument.
/// </returns>
public static ArgumentOutOfRangeException CreateArgumentOutOfRangeException(string parameterName, object actualValue, string message)
=> new(parameterName, actualValue, message);
/// <summary>
/// Creates a <see cref="NotSupportedException"/> for read-only collection modification calls.
/// </summary>
/// <returns>The NotSupportedException object</returns>
public static NotSupportedException CreateReadOnlyCollectionNotModifiableException()
=> new("This is a Read Only Collection and can not be modified");
/// <summary>
/// Parse a string into an <see cref="int"/> value
/// </summary>
/// <param name="s">the string to parse</param>
/// <param name="val">out param where the parsed value is placed</param>
/// <returns><see langword="true"/> if the string was parsed into an integer</returns>
/// <remarks>
/// <para>
/// Attempts to parse the string into an integer. If the string cannot
/// be parsed then this method returns <see langword="false"/>. The method does not throw an exception.
/// </para>
/// </remarks>
public static bool TryParse(string s, out int val)
{
val = 0;
try
{
if (double.TryParse(s, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out double doubleVal))
{
val = Convert.ToInt32(doubleVal);
return true;
}
}
catch (Exception e) when (!e.IsFatal())
{
// Ignore exception, just return false
}
return false;
}
/// <summary>
/// Parse a string into an <see cref="long"/> value
/// </summary>
/// <param name="s">the string to parse</param>
/// <param name="val">out param where the parsed value is placed</param>
/// <returns><see langword="true"/> if the string was parsed into an integer</returns>
/// <remarks>
/// <para>
/// Attempts to parse the string into an integer. If the string cannot
/// be parsed then this method returns <see langword="false"/>. The method does not throw an exception.
/// </para>
/// </remarks>
public static bool TryParse(string s, out long val)
{
val = 0;
try
{
if (double.TryParse(s, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out double doubleVal))
{
val = Convert.ToInt64(doubleVal);
return true;
}
}
catch (Exception e) when (!e.IsFatal())
{
// Ignore exception, just return false
}
return false;
}
/// <summary>
/// Parse a string into an <see cref="short"/> value
/// </summary>
/// <param name="s">the string to parse</param>
/// <param name="val">out param where the parsed value is placed</param>
/// <returns><see langword="true"/> if the string was parsed into an integer</returns>
/// <remarks>
/// <para>
/// Attempts to parse the string into an integer. If the string cannot
/// be parsed then this method returns <see langword="false"/>. The method does not throw an exception.
/// </para>
/// </remarks>
public static bool TryParse(string s, out short val)
{
val = 0;
try
{
if (double.TryParse(s, System.Globalization.NumberStyles.Integer, System.Globalization.CultureInfo.InvariantCulture, out double doubleVal))
{
val = Convert.ToInt16(doubleVal);
return true;
}
}
catch (Exception e) when (!e.IsFatal())
{
// Ignore exception, just return false
}
return false;
}
/// <summary>
/// Lookup an application setting
/// </summary>
/// <param name="key">the application settings key to lookup</param>
/// <returns>the value for the key, or <see langword="null"/></returns>
public static string? GetAppSetting(string key)
{
// Android does not support config files, and neither does a runtime that has trimmed the
// configuration system away.
if (IsAndroid || _configurationSystemUnavailable)
return Environment.GetEnvironmentVariable(key);
try
{
return ReadAppSetting(key);
}
catch (Exception e) when (!e.IsFatal())
{
if (IsMissingConfigurationSystem(e))
{
// There is no configuration system to read - Native AOT trims System.Configuration away,
// and a native process hosting the runtime has no entry assembly for it to derive the
// config file path from. That is a property of the host rather than a fault, so it is not
// reported as an error, and the environment stands in for the config file as on Android.
_configurationSystemUnavailable = true;
LogLog.Debug(_declaringType,
"No configuration system on this host. Using environment variables for application settings.", e);
return Environment.GetEnvironmentVariable(key);
}
// The config file itself does not parse. Report it and treat the setting as absent, without
// falling back to the environment - a broken config file must not silently change where
// settings come from.
LogLog.Error(_declaringType, "Exception while reading ConfigurationSettings. Check your .config file is well formed XML.", e);
}
return null;
}
/// <summary>
/// Determines whether <paramref name="exception"/> means that there is no configuration system
/// on this runtime, as opposed to a configuration file that does not parse.
/// </summary>
/// <param name="exception">the exception thrown while reading an application setting</param>
/// <returns><see langword="true"/> if the configuration system itself is unavailable</returns>
/// <remarks>
/// <para>
/// The inner exceptions have to be walked, because the runtime surfaces both cases as a
/// <see cref="ConfigurationErrorsException"/> - the very type a malformed file produces. What
/// distinguishes them is further down the chain: under Native AOT a
/// <see cref="MissingMethodException"/> for <c>ClientConfigurationHost</c>, whose constructor the
/// trimmer removed, and in a native process hosting the runtime a
/// <see cref="PlatformNotSupportedException"/> from <c>ClientConfigPaths</c>.
/// </para>
/// <para>
/// An unrecognized failure is treated as a configuration file problem, which is the safer way
/// round: it is reported rather than silently swallowed.
/// </para>
/// </remarks>
private static bool IsMissingConfigurationSystem(Exception? exception)
{
// Native AOT is what this exists for, and it identifies itself without any guesswork:
// GetCallingAssembly is unsupported there for the same reason the configuration system cannot
// initialize, so no configuration file can be read whatever the exception happens to be.
if (!CallerAssembly.IsSupported)
{
return true;
}
// Anywhere else, only a failure that names System.Configuration itself counts. A failure that
// names anything else belongs to the application's own configuration and has to keep being
// reported as an error rather than silently redirecting every setting to the environment.
for (; exception is not null; exception = exception.InnerException)
{
switch (exception)
{
// The configuration system cannot work out where the config file is, because there is no
// entry assembly to derive its path from. That is what a native process hosting the runtime
// looks like, and no config file can be read there however well formed it is. A malformed
// file never produces this, so it needs no check on which assembly it came from.
case PlatformNotSupportedException:
case FileNotFoundException { FileName: string fileName }
when IsConfigurationSystem(fileName):
case TypeLoadException { TypeName: string typeName }
when IsConfigurationSystem(typeName):
return true;
}
}
return false;
}
private static bool IsConfigurationSystem(string name)
=> name.StartsWith("System.Configuration", StringComparison.Ordinal);
/// <summary>
/// Reads a single application setting.
/// </summary>
/// <param name="key">the application settings key to lookup</param>
/// <returns>the value for the key, or <see langword="null"/></returns>
/// <remarks>
/// <para>
/// Separate from <see cref="GetAppSetting"/>, and never inlined into it, so that the failure to
/// resolve <see cref="ConfigurationManager"/> itself is raised on entry to this method - inside
/// the caller's try block - rather than on entry to <see cref="GetAppSetting"/>, where nothing
/// would catch it and a <see cref="FileNotFoundException"/> would escape the static constructor
/// as a <see cref="TypeInitializationException"/>.
/// </para>
/// <para>
/// The package declares a dependency on System.Configuration.ConfigurationManager, so this only
/// arises where the assembly is deployed by other means than the package - it costs one method
/// to keep those deployments running instead of failing at type initialization.
/// </para>
/// </remarks>
[MethodImpl(MethodImplOptions.NoInlining)]
private static string? ReadAppSetting(string key) => ConfigurationManager.AppSettings[key];
private static volatile bool _configurationSystemUnavailable;
/// <summary>
/// Convert a path into a fully qualified local file path.
/// </summary>
/// <param name="path">The path to convert.</param>
/// <returns>The fully qualified path.</returns>
/// <remarks>
/// <para>
/// Converts the path specified to a fully
/// qualified path. If the path is relative it is
/// taken as relative from the application base
/// directory.
/// </para>
/// <para>
/// The path specified must be a local file path, a URI is not supported.
/// </para>
/// </remarks>
public static string ConvertToFullPath(string path)
{
path.EnsureNotNull();
string baseDirectory = string.Empty;
try
{
string applicationBaseDirectory = ApplicationBaseDirectory;
// applicationBaseDirectory may be a URI not a local file path
Uri applicationBaseDirectoryUri = new(applicationBaseDirectory);
if (applicationBaseDirectoryUri.IsFile)
{
baseDirectory = applicationBaseDirectoryUri.LocalPath;
}
}
catch (Exception e) when (!e.IsFatal())
{
// Ignore URI exceptions & SecurityExceptions from SystemInfo.ApplicationBaseDirectory
}
if (!string.IsNullOrEmpty(baseDirectory))
{
// Note that Path.Combine will return the second path if it is rooted
return Path.GetFullPath(Path.Combine(baseDirectory, path));
}
return Path.GetFullPath(path);
}
/// <summary>
/// Creates a new case-insensitive instance of the <see cref="Hashtable"/> class with the default initial capacity.
/// </summary>
/// <returns>A new case-insensitive instance of the <see cref="Hashtable"/> class with the default initial capacity</returns>
/// <remarks>
/// <para>
/// The new Hashtable instance uses the default load factor, the CaseInsensitiveHashCodeProvider, and the CaseInsensitiveComparer.
/// </para>
/// </remarks>
public static Hashtable CreateCaseInsensitiveHashtable() => new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Tests two strings for equality, the ignoring case.
/// </summary>
/// <remarks>
/// If the platform permits, culture information is ignored completely (ordinal comparison).
/// The aim of this method is to provide a fast comparison that deals with <see langword="null"/> and ignores different casing.
/// It is not supposed to deal with various, culture-specific habits.
/// Use it to compare against pure ASCII constants, like keywords etc.
/// </remarks>
/// <param name="a">The one string.</param>
/// <param name="b">The other string.</param>
/// <returns><see langword="true"/> if the strings are equal, <see langword="false"/> otherwise.</returns>
public static bool EqualsIgnoringCase(string? a, string? b)
=> string.Equals(a, b, StringComparison.OrdinalIgnoreCase);
/// <summary>
/// The fully qualified type of the SystemInfo class.
/// </summary>
/// <remarks>
/// Used by the internal logger to record the Type of the
/// log message.
/// </remarks>
private static readonly Type _declaringType = typeof(SystemInfo);
/// <summary>
/// Cache the host name for the current machine
/// </summary>
private static string? _sHostName;
/// <summary>
/// Cache the application friendly name
/// </summary>
private static string? _sAppFriendlyName;
}