Skip to content
This repository was archived by the owner on Jan 23, 2023. It is now read-only.

Commit e69e026

Browse files
authored
Adding support for ncurses 6.1 TERM format on System.Console (#27109)
* Adding support for ncurses 6.1 TERM format on System.Console * PR Feedback * Adding test that verifies that TermInfo can be parsed both in the legacy case and new format * Changes on TermInfo * PR Feedback
1 parent 8de529d commit e69e026

File tree

6 files changed

+64
-13
lines changed

6 files changed

+64
-13
lines changed

src/System.Console/src/Resources/Strings.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,7 @@
258258
<data name="IO_PathTooLong_Path" xml:space="preserve">
259259
<value>The path '{0}' is too long, or a component of the specified path is too long.</value>
260260
</data>
261+
<data name="IO_TermInfoInvalidMagicNumber" xml:space="preserve">
262+
<value>The terminfo database has an invalid magic number: '{0}'.</value>
263+
</data>
261264
</root>

src/System.Console/src/System/TermInfo.cs

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ internal sealed class Database
107107
private readonly int _stringSectionNumOffsets;
108108
/// <summary>The number of bytes in the strings table of the database.</summary>
109109
private readonly int _stringTableNumBytes;
110+
/// <summary>Whether or not to read the number section as 32-bit integers.</summary>
111+
private readonly bool _readAs32Bit;
112+
/// <summary>The size of the integers on the number section.</summary>
113+
private readonly int _sizeOfInt;
110114

111115
/// <summary>Extended / user-defined entries in the terminfo database.</summary>
112116
private readonly Dictionary<string, string> _extendedStrings;
@@ -119,11 +123,14 @@ private Database(string term, byte[] data)
119123
_term = term;
120124
_data = data;
121125

122-
// See "man term" for the file format.
123-
if (ReadInt16(data, 0) != 0x11A) // magic number octal 0432
124-
{
125-
throw new InvalidOperationException(SR.IO_TermInfoInvalid);
126-
}
126+
const int MagicLegacyNumber = 0x11A; // magic number octal 0432 for legacy ncurses terminfo
127+
const int Magic32BitNumber = 0x21E; // magic number octal 01036 for new ncruses terminfo
128+
short magic = ReadInt16(data, 0);
129+
_readAs32Bit =
130+
magic == MagicLegacyNumber ? false :
131+
magic == Magic32BitNumber ? true :
132+
throw new InvalidOperationException(SR.Format(SR.IO_TermInfoInvalidMagicNumber, String.Concat("O" + Convert.ToString(magic, 8)))); // magic number was not recognized. Printing the magic number in octal.
133+
_sizeOfInt = (_readAs32Bit) ? 4 : 2;
127134

128135
_nameSectionNumBytes = ReadInt16(data, 2);
129136
_boolSectionNumBytes = ReadInt16(data, 4);
@@ -147,7 +154,7 @@ private Database(string term, byte[] data)
147154
// (Note that the extended section also includes other Booleans and numbers, but we don't
148155
// have any need for those now, so we don't parse them.)
149156
int extendedBeginning = RoundUpToEven(StringsTableOffset + _stringTableNumBytes);
150-
_extendedStrings = ParseExtendedStrings(data, extendedBeginning) ?? new Dictionary<string, string>();
157+
_extendedStrings = ParseExtendedStrings(data, extendedBeginning, _readAs32Bit) ?? new Dictionary<string, string>();
151158
}
152159

153160
/// <summary>The name of the associated terminfo, if any.</summary>
@@ -278,7 +285,7 @@ private static Database ReadDatabase(string term, string directoryPath)
278285
/// The offset into data where the string offsets section begins. We index into this section
279286
/// to find the location within the strings table where a string value exists.
280287
/// </summary>
281-
private int StringOffsetsOffset { get { return NumbersOffset + (_numberSectionNumShorts * 2); } }
288+
private int StringOffsetsOffset { get { return NumbersOffset + (_numberSectionNumShorts * _sizeOfInt); } }
282289

283290
/// <summary>The offset into data where the string table exists.</summary>
284291
private int StringsTableOffset { get { return StringOffsetsOffset + (_stringSectionNumOffsets * 2); } }
@@ -346,9 +353,10 @@ public int GetNumber(WellKnownNumbers numberIndex)
346353
/// defined as the earlier portions, and may not even exist, the parsing is more lenient about
347354
/// errors, returning an empty collection rather than throwing.
348355
/// </returns>
349-
private static Dictionary<string, string> ParseExtendedStrings(byte[] data, int extendedBeginning)
356+
private static Dictionary<string, string> ParseExtendedStrings(byte[] data, int extendedBeginning, bool readAs32Bit)
350357
{
351358
const int ExtendedHeaderSize = 10;
359+
int sizeOfIntValuesInBytes = (readAs32Bit) ? 4 : 2;
352360
if (extendedBeginning + ExtendedHeaderSize >= data.Length)
353361
{
354362
// Exit out as there's no extended information.
@@ -357,10 +365,10 @@ private static Dictionary<string, string> ParseExtendedStrings(byte[] data, int
357365

358366
// Read in extended counts, and exit out if we got any incorrect info
359367
int extendedBoolCount = ReadInt16(data, extendedBeginning);
360-
int extendedNumberCount = ReadInt16(data, extendedBeginning + 2);
361-
int extendedStringCount = ReadInt16(data, extendedBeginning + 4);
362-
int extendedStringNumOffsets = ReadInt16(data, extendedBeginning + 6);
363-
int extendedStringTableByteSize = ReadInt16(data, extendedBeginning + 8);
368+
int extendedNumberCount = ReadInt16(data, extendedBeginning + (2 * 1));
369+
int extendedStringCount = ReadInt16(data, extendedBeginning + (2 * 2));
370+
int extendedStringNumOffsets = ReadInt16(data, extendedBeginning + (2 * 3));
371+
int extendedStringTableByteSize = ReadInt16(data, extendedBeginning + (2 * 4));
364372
if (extendedBoolCount < 0 ||
365373
extendedNumberCount < 0 ||
366374
extendedStringCount < 0 ||
@@ -380,7 +388,7 @@ private static Dictionary<string, string> ParseExtendedStrings(byte[] data, int
380388
extendedBeginning + // go past the normal data
381389
ExtendedHeaderSize + // and past the extended header
382390
RoundUpToEven(extendedBoolCount) + // and past all of the extended Booleans
383-
(extendedNumberCount * 2); // and past all of the extended numbers
391+
(extendedNumberCount * sizeOfIntValuesInBytes); // and past all of the extended numbers
384392

385393
// Get the location where the extended string table begins. This area contains
386394
// null-terminated strings.
@@ -447,6 +455,14 @@ private static Dictionary<string, string> ParseExtendedStrings(byte[] data, int
447455

448456
private static int RoundUpToEven(int i) { return i % 2 == 1 ? i + 1 : i; }
449457

458+
/// <summary>Read a 16-bit or 32-bit value from the buffer starting at the specified position.</summary>
459+
/// <param name="buffer">The buffer from which to read.</param>
460+
/// <param name="pos">The position at which to read.</param>
461+
/// <param name="readAs32Bit">Whether or not to read value as 32-bit. Will read as 16-bit if set to false.</param>
462+
/// <returns>The value read.</returns>
463+
private static int ReadInt(byte[] buffer, int pos, bool readAs32Bit) =>
464+
readAs32Bit ? ReadInt32(buffer, pos) : ReadInt16(buffer, pos);
465+
450466
/// <summary>Read a 16-bit value from the buffer starting at the specified position.</summary>
451467
/// <param name="buffer">The buffer from which to read.</param>
452468
/// <param name="pos">The position at which to read.</param>
@@ -458,6 +474,18 @@ private static short ReadInt16(byte[] buffer, int pos)
458474
((int)buffer[pos] & 0xff)));
459475
}
460476

477+
/// <summary>Read a 32-bit value from the buffer starting at the specified position.</summary>
478+
/// <param name="buffer">The buffer from which to read.</param>
479+
/// <param name="pos">The position at which to read.</param>
480+
/// <returns>The 32-bit value read.</returns>
481+
private static int ReadInt32(byte[] buffer, int pos)
482+
{
483+
return (int)((buffer[pos] & 0xff) |
484+
buffer[pos + 1] << 8 |
485+
buffer[pos + 2] << 16 |
486+
buffer[pos + 3] << 24);
487+
}
488+
461489
/// <summary>Reads a string from the buffer starting at the specified position.</summary>
462490
/// <param name="buffer">The buffer from which to read.</param>
463491
/// <param name="pos">The position at which to read.</param>

src/System.Console/tests/System.Console.Tests.csproj

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@
3636
</Compile>
3737
<Compile Include="WindowAndCursorProps.cs" />
3838
</ItemGroup>
39+
<ItemGroup>
40+
<SupplementalTestData Include="$(MSBuildThisFileDirectory)TestData\**\*.*">
41+
<Link>%(RecursiveDir)%(Filename)%(Extension)</Link>
42+
</SupplementalTestData>
43+
</ItemGroup>
3944
<ItemGroup Condition="'$(TargetsWindows)' == 'true'">
4045
<Compile Include="ConsoleEncoding.Windows.cs" />
4146
</ItemGroup>
@@ -55,5 +60,11 @@
5560
<Name>RemoteExecutorConsoleApp</Name>
5661
</ProjectReference>
5762
</ItemGroup>
63+
<ItemGroup>
64+
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
65+
</ItemGroup>
66+
<ItemGroup>
67+
<Folder Include="ncursesFormats\" />
68+
</ItemGroup>
5869
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
5970
</Project>

src/System.Console/tests/TermInfo.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,15 @@ public void VerifyInstalledTermInfosParse()
5858
Assert.True(foundAtLeastOne, "Didn't find any terminfo files");
5959
}
6060

61+
[Fact]
62+
[PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
63+
public void VerifyTermInfoSupportsNewAndLegacyNcurses()
64+
{
65+
MethodInfo readDbMethod = typeof(Console).GetTypeInfo().Assembly.GetType(TerminfoDatabaseType).GetTypeInfo().GetDeclaredMethods(ReadDatabaseMethod).Where(m => m.GetParameters().Count() == 2).Single();
66+
readDbMethod.Invoke(null, new object[] { "xterm", "ncursesFormats" }); // This will throw InvalidOperationException in case we don't support the legacy format
67+
readDbMethod.Invoke(null, new object[] { "screen-256color", "ncursesFormats" }); // This will throw InvalidOperationException if we can't parse the new format
68+
}
69+
6170
[Theory]
6271
[PlatformSpecific(TestPlatforms.AnyUnix)] // Tests TermInfo
6372
[InlineData("xterm-256color", "\u001B\u005B\u00330m", "\u001B\u005B\u00340m", 0)]
Binary file not shown.
3.53 KB
Binary file not shown.

0 commit comments

Comments
 (0)