| File: System\TermInfo.DatabaseFactory.cs | Web Access |
| Project: src\runtime\src\libraries\System.Console\src\System.Console.csproj (System.Console) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics.CodeAnalysis; using System.IO; using Microsoft.Win32.SafeHandles; namespace System; internal static partial class TermInfo { internal sealed class DatabaseFactory { /// <summary> /// The default locations in which to search for terminfo databases. /// This is the ordering of well-known locations used by ncurses. /// </summary> internal static readonly string[] SystemTermInfoLocations = { "/etc/terminfo", "/lib/terminfo", "/usr/share/terminfo", "/usr/share/misc/terminfo", "/usr/local/share/terminfo" }; internal static string? HomeTermInfoLocation { get { string? home = PersistedFiles.GetHomeDirectory(); return home is null ? null : home + "/.terminfo"; } } internal static string? EnvVarTermInfoLocation => Environment.GetEnvironmentVariable("TERMINFO"); /// <summary>Read the database for the current terminal as specified by the "TERM" environment variable.</summary> /// <returns>The database, or null if it could not be found.</returns> internal static Database? ReadActiveDatabase() { string? term = Environment.GetEnvironmentVariable("TERM"); return !string.IsNullOrEmpty(term) ? ReadDatabase(term) : null; } /// <summary>Read the database for the specified terminal.</summary> /// <param name="term">The identifier for the terminal.</param> /// <returns>The database, or null if it could not be found.</returns> internal static Database? ReadDatabase(string term) { // This follows the same search order as prescribed by ncurses. Database? db; // First try a location specified in the TERMINFO environment variable. string? terminfo = EnvVarTermInfoLocation; if ((db = ReadDatabase(term, terminfo)) != null) { return db; } // Then try in the user's home directory. terminfo = HomeTermInfoLocation; if ((db = ReadDatabase(term, terminfo)) != null) { return db; } // Then try a set of well-known locations. foreach (string terminfoLocation in SystemTermInfoLocations) { if ((db = ReadDatabase(term, terminfoLocation)) != null) { return db; } } // Couldn't find one return null; } /// <summary>Attempt to open as readonly the specified file path.</summary> /// <param name="filePath">The path to the file to open.</param> /// <param name="fd">If successful, the opened file descriptor; otherwise, -1.</param> /// <returns>true if the file was successfully opened; otherwise, false.</returns> private static bool TryOpen(string filePath, [NotNullWhen(true)] out SafeFileHandle? fd) { fd = Interop.Sys.Open(filePath, Interop.Sys.OpenFlags.O_RDONLY | Interop.Sys.OpenFlags.O_CLOEXEC, 0); if (fd.IsInvalid) { // Don't throw in this case, as we'll be polling multiple locations looking for the file. fd.Dispose(); fd = null; return false; } return true; } /// <summary>Read the database for the specified terminal from the specified directory.</summary> /// <param name="term">The identifier for the terminal.</param> /// <param name="directoryPath">The path to the directory containing terminfo database files.</param> /// <returns>The database, or null if it could not be found.</returns> internal static unsafe Database? ReadDatabase(string? term, string? directoryPath) { if (string.IsNullOrEmpty(term) || string.IsNullOrEmpty(directoryPath)) { return null; } Span<char> stackBuffer = stackalloc char[256]; SafeFileHandle? fd; if (!TryOpen(string.Create(null, stackBuffer, $"{directoryPath}/{term[0]}/{term}"), out fd) && // /directory/termFirstLetter/term (Linux) !TryOpen(string.Create(null, stackBuffer, $"{directoryPath}/{(int)term[0]:X}/{term}"), out fd)) // /directory/termFirstLetterAsHex/term (Mac) { return null; } using (fd) { // Read in all of the terminfo data long termInfoLength = RandomAccess.GetLength(fd); const int HeaderLength = 12; if (termInfoLength <= HeaderLength) { throw new InvalidOperationException(SR.IO_TermInfoInvalid); } byte[] data = new byte[(int)termInfoLength]; long fileOffset = 0; do { int bytesRead = RandomAccess.Read(fd, new Span<byte>(data, (int)fileOffset, (int)(termInfoLength - fileOffset)), fileOffset); if (bytesRead == 0) { throw new InvalidOperationException(SR.IO_TermInfoInvalid); } fileOffset += bytesRead; } while (fileOffset < termInfoLength); // Create the database from the data return new Database(term, data); } } } }