[SACDRV]: IReadLast returns WCHAR, not CHAR.

[SACDRV]: Implement VtUtf8 case in ChannelInitializeVTable.
[SACDRV]: Implement DoHelpCommand, reformat concmd.c to standards.
[SACDRV]: ConMgrInitialize shoudl setup the SAC channel as VtUtf8, not Raw. I guess it was set to Raw since VtUtf8 wasn't yet implemented, however this breaks the command parsing since it expects WCHAR's. Make a note of this, and temporarily hack ConMgrSerialPortConsumer to work with CHAR's from a Raw channel.
[SACDRV]: Implement ConMgrProcessInputLine, which calls out the appopriate helpers.
[SACDRV]: Fixes to parsing in ConMgrSerialPortConsumer
[SACDRV]: Document, reformat, and fix some of the bugs in the RawChannelI* commands.
[SACDRV]: Implement simple (ANSI) case of SacTranslateUtf8ToUnicode.
[SACDRV]: Implement SacFormatMessage and GetMessageLineCount.
[SACDRV]: Start implementing VT-UTF8 support. Input routines are done, output routines are still WIP.
[SACMSG]: Fix the messages in the .MC file. Stuff is now being output correctly. If using Putty, make sure to turn on "Implicit CR in every LF" in Settings->Terminal.

svn path=/trunk/; revision=59778
This commit is contained in:
Alex Ionescu 2013-08-19 03:32:39 +00:00
parent ed35b983f9
commit 519595d9c4
9 changed files with 1112 additions and 359 deletions

View file

@ -404,7 +404,6 @@ ChanMgrCreateChannel(OUT PSAC_CHANNEL *Channel,
if (i == SAC_MAX_CHANNELS) if (i == SAC_MAX_CHANNELS)
{ {
/* Bail out */ /* Bail out */
SAC_DBG(SAC_DBG_INIT, "failing here: %d %lx\n", __LINE__, Status);
goto ReturnStatus; goto ReturnStatus;
} }
@ -438,7 +437,6 @@ ChanMgrCreateChannel(OUT PSAC_CHANNEL *Channel,
else else
{ {
/* We couldn't create it, free the buffer */ /* We couldn't create it, free the buffer */
SAC_DBG(SAC_DBG_INIT, "failing here: %d %lx\n", __LINE__, Status);
SacFreePool(NewChannel); SacFreePool(NewChannel);
} }

View file

@ -147,11 +147,11 @@ ChannelIRead(IN PSAC_CHANNEL Channel,
return Status; return Status;
} }
UCHAR WCHAR
NTAPI NTAPI
ChannelIReadLast(IN PSAC_CHANNEL Channel) ChannelIReadLast(IN PSAC_CHANNEL Channel)
{ {
UCHAR LastChar; WCHAR LastChar;
/* Read the last character while holding the lock */ /* Read the last character while holding the lock */
ChannelLockIBuffer(Channel); ChannelLockIBuffer(Channel);
@ -410,9 +410,19 @@ ChannelInitializeVTable(IN PSAC_CHANNEL Channel)
switch (Channel->ChannelType) switch (Channel->ChannelType)
{ {
case VtUtf8: case VtUtf8:
/* FIXME: TODO */ /* Setup the calls for a VT-UTF8 channel */
ASSERT(FALSE); Channel->ChannelCreate = VTUTF8ChannelCreate;
return STATUS_NOT_IMPLEMENTED; Channel->ChannelDestroy = VTUTF8ChannelDestroy;
Channel->ChannelOutputFlush = VTUTF8ChannelOFlush;
Channel->ChannelOutputEcho = VTUTF8ChannelOEcho;
Channel->ChannelOutputWrite = VTUTF8ChannelOWrite;
Channel->ChannelOutputRead = VTUTF8ChannelORead;
Channel->ChannelInputWrite = VTUTF8ChannelIWrite;
Channel->ChannelInputRead = VTUTF8ChannelIRead;
Channel->ChannelInputReadLast = VTUTF8ChannelIReadLast;
Channel->ChannelInputBufferIsFull = VTUTF8ChannelIBufferIsFull;
Channel->ChannelInputBufferLength = VTUTF8ChannelIBufferLength;
break;
case Cmd: case Cmd:
/* FIXME: TODO */ /* FIXME: TODO */

View file

@ -17,58 +17,44 @@ ULONG GlobalBufferSize;
/* FUNCTIONS *****************************************************************/ /* FUNCTIONS *****************************************************************/
VOID NTSTATUS
NTAPI DoChannelListCommand(
DoRebootCommand(IN BOOLEAN Reboot) VOID
)
{ {
LARGE_INTEGER Timeout, TickCount; return STATUS_NOT_IMPLEMENTED;
NTSTATUS Status; }
KEVENT Event;
SAC_DBG(1, "SAC DoRebootCommand: Entering.\n");
/* Get the current time now, and setup a timeout in 1 second */ NTSTATUS
KeQueryTickCount(&TickCount); DoChannelCloseByNameCommand(
Timeout.QuadPart = TickCount.QuadPart / (10000000 / KeQueryTimeIncrement()); IN PCHAR Count
)
{
return STATUS_NOT_IMPLEMENTED;
}
/* Check if the timeout is small enough */ NTSTATUS
if (Timeout.QuadPart < 60 ) DoChannelCloseByIndexCommand(
{ IN ULONG ChannelIndex
/* Show the prompt */ )
ConMgrSimpleEventMessage(Reboot ? {
SAC_RESTART_PROMPT : SAC_SHUTDOWN_PROMPT, return STATUS_NOT_IMPLEMENTED;
TRUE); }
/* Do the wait */ NTSTATUS
KeInitializeEvent(&Event, SynchronizationEvent, 0); DoChannelSwitchByNameCommand(
Timeout.QuadPart = -10000000 * (60 - Timeout.LowPart); IN PCHAR Count
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &Timeout); )
} {
return STATUS_NOT_IMPLEMENTED;
}
/* Do a shutdown or a reboot, based on the request */ NTSTATUS
Status = NtShutdownSystem(Reboot ? ShutdownReboot : ShutdownPowerOff); DoChannelSwitchByIndexCommand(
IN ULONG ChannelIndex
/* Check if anyone in the command channel already allocated this */ )
if (!GlobalBuffer) {
{ return STATUS_NOT_IMPLEMENTED;
/* Allocate it */
GlobalBuffer = SacAllocatePool(PAGE_SIZE, GLOBAL_BLOCK_TAG);
if (!GlobalBuffer)
{
/* We need the global buffer, bail out without it*/
SacPutSimpleMessage(SAC_OUT_OF_MEMORY_PROMPT);
SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoRebootCommand: Exiting (1).\n");
return;
}
/* Set the size of the buffer */
GlobalBufferSize = PAGE_SIZE;
}
/* We came back from a reboot, this doesn't make sense, tell the user */
SacPutSimpleMessage(Reboot ? SAC_RESTART_FAIL_PROMPT : SAC_SHUTDOWN_FAIL_PROMPT);
swprintf(GlobalBuffer, GetMessage(SAC_FAIL_PROMPT), Status);
SacPutString(GlobalBuffer);
SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoRebootCommand: Exiting.\n");
} }
NTSTATUS NTSTATUS
@ -124,169 +110,201 @@ CallQueryIPIOCTL(
} }
VOID VOID
DoFullInfoCommand( NTAPI
VOID DoRebootCommand(IN BOOLEAN Reboot)
) {
LARGE_INTEGER Timeout, TickCount;
NTSTATUS Status;
KEVENT Event;
SAC_DBG(1, "SAC DoRebootCommand: Entering.\n");
/* Get the current time now, and setup a timeout in 1 second */
KeQueryTickCount(&TickCount);
Timeout.QuadPart = TickCount.QuadPart / (10000000 / KeQueryTimeIncrement());
/* Check if the timeout is small enough */
if (Timeout.QuadPart < 60 )
{
/* Show the prompt */
ConMgrSimpleEventMessage(Reboot ?
SAC_RESTART_PROMPT : SAC_SHUTDOWN_PROMPT,
TRUE);
/* Do the wait */
KeInitializeEvent(&Event, SynchronizationEvent, 0);
Timeout.QuadPart = -10000000 * (60 - Timeout.LowPart);
KeWaitForSingleObject(&Event, Executive, KernelMode, FALSE, &Timeout);
}
/* Do a shutdown or a reboot, based on the request */
Status = NtShutdownSystem(Reboot ? ShutdownReboot : ShutdownPowerOff);
/* Check if anyone in the command channel already allocated this */
if (!GlobalBuffer)
{
/* Allocate it */
GlobalBuffer = SacAllocatePool(PAGE_SIZE, GLOBAL_BLOCK_TAG);
if (!GlobalBuffer)
{
/* We need the global buffer, bail out without it*/
SacPutSimpleMessage(SAC_OUT_OF_MEMORY_PROMPT);
SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoRebootCommand: Exiting (1).\n");
return;
}
/* Set the size of the buffer */
GlobalBufferSize = PAGE_SIZE;
}
/* We came back from a reboot, this doesn't make sense, tell the user */
SacPutSimpleMessage(Reboot ? SAC_RESTART_FAIL_PROMPT : SAC_SHUTDOWN_FAIL_PROMPT);
swprintf(GlobalBuffer, GetMessage(SAC_FAIL_PROMPT), Status);
SacPutString(GlobalBuffer);
SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC DoRebootCommand: Exiting.\n");
}
VOID
NTAPI
DoFullInfoCommand(VOID)
{ {
} }
VOID VOID
DoPagingCommand( NTAPI
VOID DoPagingCommand(VOID)
)
{ {
} }
VOID VOID
DoSetTimeCommand( NTAPI
IN PCHAR InputTime DoSetTimeCommand(IN PCHAR InputTime)
)
{ {
} }
VOID VOID
DoKillCommand( NTAPI
IN PCHAR KillString DoKillCommand(IN PCHAR KillString)
)
{ {
} }
VOID VOID
DoLowerPriorityCommand( NTAPI
IN PCHAR PrioString DoLowerPriorityCommand(IN PCHAR PrioString)
)
{ {
} }
VOID VOID
DoRaisePriorityCommand( NTAPI
IN PCHAR PrioString DoRaisePriorityCommand(IN PCHAR PrioString)
)
{ {
} }
VOID VOID
DoLimitMemoryCommand( NTAPI
IN PCHAR LimitString DoLimitMemoryCommand(IN PCHAR LimitString)
)
{ {
} }
VOID VOID
DoCrashCommand( NTAPI
VOID DoCrashCommand(VOID)
)
{ {
} }
VOID VOID
DoMachineInformationCommand( NTAPI
VOID DoMachineInformationCommand(VOID)
)
{
}
NTSTATUS
DoChannelListCommand(
VOID
)
{
return STATUS_NOT_IMPLEMENTED;
}
NTSTATUS
DoChannelCloseByNameCommand(
IN PCHAR Count
)
{
return STATUS_NOT_IMPLEMENTED;
}
NTSTATUS
DoChannelCloseByIndexCommand(
IN ULONG ChannelIndex
)
{
return STATUS_NOT_IMPLEMENTED;
}
NTSTATUS
DoChannelSwitchByNameCommand(
IN PCHAR Count
)
{
return STATUS_NOT_IMPLEMENTED;
}
NTSTATUS
DoChannelSwitchByIndexCommand(
IN ULONG ChannelIndex
)
{
return STATUS_NOT_IMPLEMENTED;
}
VOID
DoChannelCommand(
IN PCHAR ChannelString
)
{ {
} }
VOID VOID
DoCmdCommand( NTAPI
VOID DoChannelCommand(IN PCHAR ChannelString)
)
{ {
} }
VOID VOID
DoLockCommand( NTAPI
VOID DoCmdCommand(IN PCHAR InputString)
)
{ {
} }
VOID VOID
DoHelpCommand( NTAPI
VOID DoLockCommand(VOID)
) {
}
#define PRINT_HELP_MESSAGE(x) \
{ \
Count += NewCount; \
NewCount = GetMessageLineCount(x); \
if ( (NewCount + Count) > SAC_VTUTF8_COL_HEIGHT) \
{ \
PutMore(&ScreenFull); \
if (ScreenFull) return; \
Count = 0; \
} \
SacPutSimpleMessage(x); \
}
VOID
NTAPI
DoHelpCommand(VOID)
{
ULONG NewCount = 0, Count = 0;
BOOLEAN ScreenFull = FALSE;
PRINT_HELP_MESSAGE(112);
PRINT_HELP_MESSAGE(12);
PRINT_HELP_MESSAGE(13);
PRINT_HELP_MESSAGE(14);
PRINT_HELP_MESSAGE(15);
PRINT_HELP_MESSAGE(16);
PRINT_HELP_MESSAGE(31);
PRINT_HELP_MESSAGE(18);
PRINT_HELP_MESSAGE(19);
PRINT_HELP_MESSAGE(32);
PRINT_HELP_MESSAGE(20);
PRINT_HELP_MESSAGE(21);
PRINT_HELP_MESSAGE(22);
PRINT_HELP_MESSAGE(23);
PRINT_HELP_MESSAGE(24);
PRINT_HELP_MESSAGE(25);
PRINT_HELP_MESSAGE(27);
PRINT_HELP_MESSAGE(28);
PRINT_HELP_MESSAGE(29);
}
VOID
NTAPI
DoGetNetInfo(IN BOOLEAN DoPrint)
{ {
} }
VOID VOID
DoGetNetInfo( NTAPI
IN BOOLEAN DoPrint DoSetIpAddressCommand(IN PCHAR IpString)
)
{ {
} }
VOID VOID
DoSetIpAddressCommand( NTAPI
IN PCHAR IpString DoTlistCommand(VOID)
)
{ {
}
VOID }
DoTlistCommand(
VOID
)
{
}

View file

@ -169,7 +169,7 @@ ConMgrInitialize(VOID)
/* Setup the attributes for the raw SAC channel */ /* Setup the attributes for the raw SAC channel */
RtlZeroMemory(&SacChannelAttributes, sizeof(SacChannelAttributes)); RtlZeroMemory(&SacChannelAttributes, sizeof(SacChannelAttributes));
SacChannelAttributes.ChannelType = Raw; SacChannelAttributes.ChannelType = Raw; /* FIXME: Should be VtUtf8 */
/* Get the right name for it */ /* Get the right name for it */
pcwch = GetMessage(SAC_CHANNEL_NAME); pcwch = GetMessage(SAC_CHANNEL_NAME);
@ -438,6 +438,7 @@ ConMgrChannelOWrite(IN PSAC_CHANNEL Channel,
/* Do the write with the lock held */ /* Do the write with the lock held */
SacAcquireMutexLock(); SacAcquireMutexLock();
ASSERT(FALSE);
Status = STATUS_NOT_IMPLEMENTED;// ChannelOWrite(Channel, WriteBuffer + 24, *(WriteBuffer + 20)); Status = STATUS_NOT_IMPLEMENTED;// ChannelOWrite(Channel, WriteBuffer + 24, *(WriteBuffer + 20));
SacReleaseMutexLock(); SacReleaseMutexLock();
@ -446,22 +447,137 @@ ConMgrChannelOWrite(IN PSAC_CHANNEL Channel,
return Status; return Status;
} }
#define Shutdown 1
#define Restart 3
#define Nothing 0
BOOLEAN GlobalPagingNeeded;
VOID VOID
NTAPI NTAPI
ConMgrProcessInputLine(VOID) ConMgrProcessInputLine(VOID)
{ {
ASSERT(FALSE); BOOLEAN EnablePaging;
} NTSTATUS Status;
#define Nothing 0 SAC_DBG(4, "SAC Input Test: %s\n", InputBuffer);
if (!strncmp(InputBuffer, "t", 1))
{
DoTlistCommand();
}
else if (!strncmp(InputBuffer, "?", 1))
{
DoHelpCommand();
}
else if (!strncmp(InputBuffer, "help", 4))
{
DoHelpCommand();
}
else if (!strncmp(InputBuffer, "f", 1))
{
DoFullInfoCommand();
}
else if (!strncmp(InputBuffer, "p", 1))
{
DoPagingCommand();
}
else if (!strncmp(InputBuffer, "id", 2))
{
DoMachineInformationCommand();
}
else if (!strncmp(InputBuffer, "crashdump", 9))
{
DoCrashCommand();
}
else if (!strncmp(InputBuffer, "lock", 4))
{
DoLockCommand();
}
else if (!strncmp(InputBuffer, "shutdown", 8))
{
ExecutePostConsumerCommand = Shutdown;
}
else if (!strncmp(InputBuffer, "restart", 7))
{
ExecutePostConsumerCommand = Restart;
}
else if (!strncmp(InputBuffer, "d", 1))
{
EnablePaging = GlobalPagingNeeded;
Status = HeadlessDispatch(HeadlessCmdDisplayLog,
&EnablePaging,
sizeof(EnablePaging),
NULL,
0);
if (!NT_SUCCESS(Status)) SAC_DBG(4, "SAC Display Log failed.\n");
}
else if (!strncmp(InputBuffer, "cmd", 3))
{
if (CommandConsoleLaunchingEnabled)
{
DoCmdCommand(InputBuffer);
}
else
{
SacPutSimpleMessage(148);
}
}
else if (!(strncmp(InputBuffer, "ch", 2)) &&
(((strlen(InputBuffer) > 1) && (InputBuffer[2] == ' ')) ||
(strlen(InputBuffer) == 2)))
{
DoChannelCommand(InputBuffer);
}
else if (!(strncmp(InputBuffer, "k", 1)) &&
(((strlen(InputBuffer) > 1) && (InputBuffer[1] == ' ')) ||
(strlen(InputBuffer) == 1)))
{
DoKillCommand(InputBuffer);
}
else if (!(strncmp(InputBuffer, "l", 1)) &&
(((strlen(InputBuffer) > 1) && (InputBuffer[1] == ' ')) ||
(strlen(InputBuffer) == 1)))
{
DoLowerPriorityCommand(InputBuffer);
}
else if (!(strncmp(InputBuffer, "r", 1)) &&
(((strlen(InputBuffer) > 1) && (InputBuffer[1] == ' ')) ||
(strlen(InputBuffer) == 1)))
{
DoRaisePriorityCommand(InputBuffer);
}
else if (!(strncmp(InputBuffer, "m", 1)) &&
(((strlen(InputBuffer) > 1) && (InputBuffer[1] == ' ')) ||
(strlen(InputBuffer) == 1)))
{
DoLimitMemoryCommand(InputBuffer);
}
else if (!(strncmp(InputBuffer, "s", 1)) &&
(((strlen(InputBuffer) > 1) && (InputBuffer[1] == ' ')) ||
(strlen(InputBuffer) == 1)))
{
DoSetTimeCommand(InputBuffer);
}
else if (!(strncmp(InputBuffer, "i", 1)) &&
(((strlen(InputBuffer) > 1) && (InputBuffer[1] == ' ')) ||
(strlen(InputBuffer) == 1)))
{
DoSetIpAddressCommand(InputBuffer);
}
else if ((InputBuffer[0] != '\n') && (InputBuffer[0] != ANSI_NULL))
{
SacPutSimpleMessage(105);
}
}
VOID VOID
NTAPI NTAPI
ConMgrSerialPortConsumer(VOID) ConMgrSerialPortConsumer(VOID)
{ {
NTSTATUS Status; NTSTATUS Status;
CHAR Char, LastChar; CHAR Char;
CHAR WriteBuffer[2], ReadBuffer[2]; WCHAR LastChar;
CHAR ReadBuffer[2];
ULONG ReadBufferSize, i; ULONG ReadBufferSize, i;
WCHAR StringBuffer[2]; WCHAR StringBuffer[2];
SAC_DBG(SAC_DBG_MACHINE, "SAC TimerDpcRoutine: Entering.\n"); //bug SAC_DBG(SAC_DBG_MACHINE, "SAC TimerDpcRoutine: Entering.\n"); //bug
@ -546,8 +662,8 @@ ConMgrSerialPortConsumer(VOID)
if ((InputInEscape) && (CurrentChannel != SacChannel)) if ((InputInEscape) && (CurrentChannel != SacChannel))
{ {
/* Store the ESC in the current channel buffer */ /* Store the ESC in the current channel buffer */
WriteBuffer[0] = '\x1B'; ReadBuffer[0] = '\x1B';
ChannelIWrite(CurrentChannel, WriteBuffer, sizeof(CHAR)); ChannelIWrite(CurrentChannel, ReadBuffer, sizeof(CHAR));
} }
/* Check if we are no longer pressing ESC and exit the mode if so */ /* Check if we are no longer pressing ESC and exit the mode if so */
@ -582,26 +698,26 @@ DoLineParsing:
ChannelIReadLast(CurrentChannel); ChannelIReadLast(CurrentChannel);
/* NULL-terminate the channel's input buffer */ /* NULL-terminate the channel's input buffer */
WriteBuffer[0] = ANSI_NULL; ReadBuffer[0] = ANSI_NULL;
ChannelIWrite(CurrentChannel, WriteBuffer, sizeof(CHAR)); ChannelIWrite(CurrentChannel, ReadBuffer, sizeof(CHAR));
/* Loop over every last character */ /* Loop over every last character */
do do
{ {
/* Read every character in the channel, and strip whitespace */ /* Read every character in the channel, and strip whitespace */
LastChar = ChannelIReadLast(CurrentChannel); LastChar = ChannelIReadLast(CurrentChannel);
WriteBuffer[0] = LastChar; ReadBuffer[0] = (CHAR) LastChar;
} while ((!(LastChar) || } while ((!(LastChar) ||
(LastChar == ' ') || (LastChar == L' ') ||
(LastChar == '\t')) && (LastChar == L'\t')) &&
(ChannelIBufferLength(CurrentChannel))); (ChannelIBufferLength(CurrentChannel)));
/* Write back into the channel the last character */ /* Write back into the channel the last character */
ChannelIWrite(CurrentChannel, WriteBuffer, sizeof(CHAR)); ChannelIWrite(CurrentChannel, ReadBuffer, sizeof(CHAR));
/* NULL-terminate the input buffer */ /* NULL-terminate the input buffer */
WriteBuffer[0] = ANSI_NULL; ReadBuffer[0] = ANSI_NULL;
ChannelIWrite(CurrentChannel, WriteBuffer, sizeof(WCHAR)); ChannelIWrite(CurrentChannel, ReadBuffer, sizeof(CHAR));
/* Now loop over every first character */ /* Now loop over every first character */
do do
@ -609,11 +725,10 @@ DoLineParsing:
/* Read every character in the channel, and strip whitespace */ /* Read every character in the channel, and strip whitespace */
ChannelIRead(CurrentChannel, ChannelIRead(CurrentChannel,
ReadBuffer, ReadBuffer,
sizeof(ReadBuffer), sizeof(CHAR), /* FIXME: Should be sizeof(ReadBuffer) */
&ReadBufferSize); &ReadBufferSize);
WriteBuffer[0] = ReadBuffer[0];
} while ((ReadBufferSize) && } while ((ReadBufferSize) &&
((ReadBuffer[0] != ' ') || (ReadBuffer[0] != '\t'))); ((ReadBuffer[0] == ' ') || (ReadBuffer[0] == '\t')));
/* We read one more than we should, so treat that as our first one */ /* We read one more than we should, so treat that as our first one */
InputBuffer[0] = ReadBuffer[0]; InputBuffer[0] = ReadBuffer[0];
@ -625,7 +740,7 @@ DoLineParsing:
/* Read each character -- there should be max 80 */ /* Read each character -- there should be max 80 */
ChannelIRead(CurrentChannel, ChannelIRead(CurrentChannel,
ReadBuffer, ReadBuffer,
sizeof(ReadBuffer), sizeof(CHAR), /* FIXME: Should be sizeof(ReadBuffer) */
&ReadBufferSize); &ReadBufferSize);
ASSERT(i < SAC_VTUTF8_COL_WIDTH); ASSERT(i < SAC_VTUTF8_COL_WIDTH);
InputBuffer[i++] = ReadBuffer[0]; InputBuffer[i++] = ReadBuffer[0];
@ -637,7 +752,7 @@ DoLineParsing:
/* Again it should be less than 80 characters */ /* Again it should be less than 80 characters */
ASSERT(i < SAC_VTUTF8_COL_WIDTH); ASSERT(i < SAC_VTUTF8_COL_WIDTH);
/* And upcase each character */ /* And downbase each character */
Char = InputBuffer[i]; Char = InputBuffer[i];
if ((Char >= 'A') && (Char <= 'Z')) InputBuffer[i] = Char + ' '; if ((Char >= 'A') && (Char <= 'Z')) InputBuffer[i] = Char + ' ';
} }
@ -700,9 +815,9 @@ DoLineParsing:
ChannelIReadLast(CurrentChannel); ChannelIReadLast(CurrentChannel);
ChannelIReadLast(CurrentChannel); ChannelIReadLast(CurrentChannel);
/* NULL-terminate it */ /* Write the last character that was just typed in */
WriteBuffer[0] = Char; ReadBuffer[0] = Char;
ChannelIWrite(CurrentChannel, WriteBuffer, sizeof(CHAR)); ChannelIWrite(CurrentChannel, ReadBuffer, sizeof(CHAR));
continue; continue;
} }

View file

@ -23,17 +23,19 @@ RawChannelCreate(IN PSAC_CHANNEL Channel)
{ {
CHECK_PARAMETER(Channel); CHECK_PARAMETER(Channel);
/* Allocate the output buffer */
Channel->OBuffer = SacAllocatePool(SAC_RAW_OBUFFER_SIZE, GLOBAL_BLOCK_TAG); Channel->OBuffer = SacAllocatePool(SAC_RAW_OBUFFER_SIZE, GLOBAL_BLOCK_TAG);
CHECK_ALLOCATION(Channel->OBuffer); CHECK_ALLOCATION(Channel->OBuffer);
/* Allocate the input buffer */
Channel->IBuffer = SacAllocatePool(SAC_RAW_IBUFFER_SIZE, GLOBAL_BLOCK_TAG); Channel->IBuffer = SacAllocatePool(SAC_RAW_IBUFFER_SIZE, GLOBAL_BLOCK_TAG);
CHECK_ALLOCATION(Channel->IBuffer); CHECK_ALLOCATION(Channel->IBuffer);
/* Reset all flags and return success */
Channel->OBufferIndex = 0; Channel->OBufferIndex = 0;
Channel->OBufferFirstGoodIndex = 0; Channel->OBufferFirstGoodIndex = 0;
Channel->ChannelHasNewIBufferData = FALSE; Channel->ChannelHasNewIBufferData = FALSE;
Channel->ChannelHasNewOBufferData = FALSE; Channel->ChannelHasNewOBufferData = FALSE;
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
@ -43,16 +45,9 @@ RawChannelDestroy(IN PSAC_CHANNEL Channel)
{ {
CHECK_PARAMETER(Channel); CHECK_PARAMETER(Channel);
if (Channel->OBuffer) /* Free the buffer and then destroy the channel */
{ if (Channel->OBuffer) SacFreePool(Channel->OBuffer);
SacFreePool(Channel->OBuffer); if (Channel->IBuffer) SacFreePool(Channel->IBuffer);
}
if (Channel->IBuffer)
{
SacFreePool(Channel->IBuffer);
}
return ChannelDestroy(Channel); return ChannelDestroy(Channel);
} }
@ -63,13 +58,6 @@ ChannelHasNewOBufferData(IN PSAC_CHANNEL Channel)
return Channel->ChannelHasNewOBufferData; return Channel->ChannelHasNewOBufferData;
} }
FORCEINLINE
BOOLEAN
ChannelHasNewIBufferData(IN PSAC_CHANNEL Channel)
{
return Channel->ChannelHasNewIBufferData;
}
NTSTATUS NTSTATUS
NTAPI NTAPI
RawChannelORead(IN PSAC_CHANNEL Channel, RawChannelORead(IN PSAC_CHANNEL Channel,
@ -207,38 +195,6 @@ RawChannelOFlush(IN PSAC_CHANNEL Channel)
return ConMgrFlushData(Channel); return ConMgrFlushData(Channel);
} }
ULONG
NTAPI
RawChannelGetIBufferIndex(IN PSAC_CHANNEL Channel)
{
ASSERT(Channel);
ASSERT(Channel->IBufferIndex < SAC_RAW_IBUFFER_SIZE);
return Channel->IBufferIndex;
}
VOID
NTAPI
RawChannelSetIBufferIndex(IN PSAC_CHANNEL Channel,
IN ULONG BufferIndex)
{
NTSTATUS Status;
ASSERT(Channel);
ASSERT(Channel->IBufferIndex < SAC_RAW_IBUFFER_SIZE);
Channel->IBufferIndex = BufferIndex;
Channel->ChannelHasNewIBufferData = BufferIndex != 0;
if (!Channel->IBufferIndex)
{
if (Channel->Flags & SAC_CHANNEL_FLAG_HAS_NEW_DATA_EVENT)
{
ChannelClearEvent(Channel, HasNewDataEvent);
UNREFERENCED_PARAMETER(Status);
}
}
}
NTSTATUS NTSTATUS
NTAPI NTAPI
RawChannelOWrite(IN PSAC_CHANNEL Channel, RawChannelOWrite(IN PSAC_CHANNEL Channel,
@ -256,6 +212,40 @@ RawChannelOWrite(IN PSAC_CHANNEL Channel,
return RawChannelOWrite2(Channel, String, Length); return RawChannelOWrite2(Channel, String, Length);
} }
ULONG
NTAPI
RawChannelGetIBufferIndex(IN PSAC_CHANNEL Channel)
{
ASSERT(Channel);
ASSERT(Channel->IBufferIndex < SAC_RAW_IBUFFER_SIZE);
/* Return the current buffer index */
return Channel->IBufferIndex;
}
VOID
NTAPI
RawChannelSetIBufferIndex(IN PSAC_CHANNEL Channel,
IN ULONG BufferIndex)
{
NTSTATUS Status;
ASSERT(Channel);
ASSERT(Channel->IBufferIndex < SAC_RAW_IBUFFER_SIZE);
/* Set the new index, and if it's not zero, it means we have data */
Channel->IBufferIndex = BufferIndex;
_InterlockedExchange(&Channel->ChannelHasNewIBufferData, BufferIndex != 0);
/* If we have new data, and an event has been registered... */
if (!(Channel->IBufferIndex) &&
(Channel->Flags & SAC_CHANNEL_FLAG_HAS_NEW_DATA_EVENT))
{
/* Go ahead and signal it */
ChannelClearEvent(Channel, HasNewDataEvent);
UNREFERENCED_PARAMETER(Status);
}
}
NTSTATUS NTSTATUS
NTAPI NTAPI
RawChannelIRead(IN PSAC_CHANNEL Channel, RawChannelIRead(IN PSAC_CHANNEL Channel,
@ -264,38 +254,46 @@ RawChannelIRead(IN PSAC_CHANNEL Channel,
IN PULONG ReturnBufferSize) IN PULONG ReturnBufferSize)
{ {
ULONG CopyChars; ULONG CopyChars;
CHECK_PARAMETER1(Channel); CHECK_PARAMETER1(Channel);
CHECK_PARAMETER2(Buffer); CHECK_PARAMETER2(Buffer);
CHECK_PARAMETER_WITH_STATUS(BufferSize > 0, STATUS_INVALID_BUFFER_SIZE); CHECK_PARAMETER_WITH_STATUS(BufferSize > 0, STATUS_INVALID_BUFFER_SIZE);
/* Assume failure */
*ReturnBufferSize = 0; *ReturnBufferSize = 0;
/* Check how many bytes are in the buffer */
if (Channel->ChannelInputBufferLength(Channel) == 0) if (Channel->ChannelInputBufferLength(Channel) == 0)
{ {
/* Apparently nothing. Make sure the flag indicates so too */
ASSERT(ChannelHasNewIBufferData(Channel) == FALSE); ASSERT(ChannelHasNewIBufferData(Channel) == FALSE);
} }
else else
{ {
CopyChars = Channel->ChannelInputBufferLength(Channel); /* Use the smallest number of bytes either in the buffer or requested */
if (CopyChars > BufferSize) CopyChars = BufferSize; CopyChars = min(Channel->ChannelInputBufferLength(Channel), BufferSize);
ASSERT(CopyChars <= Channel->ChannelInputBufferLength(Channel)); ASSERT(CopyChars <= Channel->ChannelInputBufferLength(Channel));
/* Copy them into the caller's buffer */
RtlCopyMemory(Buffer, Channel->IBuffer, CopyChars); RtlCopyMemory(Buffer, Channel->IBuffer, CopyChars);
/* Update the channel's index past the copied (read) bytes */
RawChannelSetIBufferIndex(Channel, RawChannelSetIBufferIndex(Channel,
RawChannelGetIBufferIndex(Channel) - CopyChars); RawChannelGetIBufferIndex(Channel) - CopyChars);
/* Are there still bytes that haven't been read yet? */
if (Channel->ChannelInputBufferLength(Channel)) if (Channel->ChannelInputBufferLength(Channel))
{ {
/* Shift them up in the buffer */
RtlMoveMemory(Channel->IBuffer, RtlMoveMemory(Channel->IBuffer,
&Channel->IBuffer[CopyChars], &Channel->IBuffer[CopyChars],
Channel->ChannelInputBufferLength(Channel)); Channel->ChannelInputBufferLength(Channel));
} }
/* Return the number of bytes we actually copied */
*ReturnBufferSize = CopyChars; *ReturnBufferSize = CopyChars;
} }
/* Return success */
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
@ -307,6 +305,7 @@ RawChannelIBufferIsFull(IN PSAC_CHANNEL Channel,
CHECK_PARAMETER1(Channel); CHECK_PARAMETER1(Channel);
CHECK_PARAMETER2(BufferStatus); CHECK_PARAMETER2(BufferStatus);
/* If the index is beyond the length, the buffer must be full */
*BufferStatus = RawChannelGetIBufferIndex(Channel) > SAC_RAW_IBUFFER_SIZE; *BufferStatus = RawChannelGetIBufferIndex(Channel) > SAC_RAW_IBUFFER_SIZE;
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
@ -316,25 +315,31 @@ NTAPI
RawChannelIBufferLength(IN PSAC_CHANNEL Channel) RawChannelIBufferLength(IN PSAC_CHANNEL Channel)
{ {
ASSERT(Channel); ASSERT(Channel);
/* The index is the current length (since we're 0-based) */
return RawChannelGetIBufferIndex(Channel); return RawChannelGetIBufferIndex(Channel);
} }
CHAR WCHAR
NTAPI NTAPI
RawChannelIReadLast(IN PSAC_CHANNEL Channel) RawChannelIReadLast(IN PSAC_CHANNEL Channel)
{ {
UCHAR LastChar = 0; UCHAR LastChar = 0;
ASSERT(Channel); ASSERT(Channel);
/* Check if there's anything to read in the buffer */
if (Channel->ChannelInputBufferLength(Channel)) if (Channel->ChannelInputBufferLength(Channel))
{ {
RawChannelSetIBufferIndex(Channel, RawChannelGetIBufferIndex(Channel) - 1); /* Go back one character */
RawChannelSetIBufferIndex(Channel,
RawChannelGetIBufferIndex(Channel) - 1);
/* Read it, and clear its current value */
LastChar = Channel->IBuffer[RawChannelGetIBufferIndex(Channel)]; LastChar = Channel->IBuffer[RawChannelGetIBufferIndex(Channel)];
Channel->IBuffer[RawChannelGetIBufferIndex(Channel)] = 0; Channel->IBuffer[RawChannelGetIBufferIndex(Channel)] = ANSI_NULL;
} }
/* Return the last character */
return LastChar; return LastChar;
} }
@ -347,27 +352,34 @@ RawChannelIWrite(IN PSAC_CHANNEL Channel,
NTSTATUS Status; NTSTATUS Status;
BOOLEAN IsFull; BOOLEAN IsFull;
ULONG Index; ULONG Index;
CHECK_PARAMETER1(Channel); CHECK_PARAMETER1(Channel);
CHECK_PARAMETER2(Buffer); CHECK_PARAMETER2(Buffer);
CHECK_PARAMETER_WITH_STATUS(BufferSize > 0, STATUS_INVALID_BUFFER_SIZE); CHECK_PARAMETER_WITH_STATUS(BufferSize > 0, STATUS_INVALID_BUFFER_SIZE);
/* First, check if the input buffer still has space */
Status = RawChannelIBufferIsFull(Channel, &IsFull); Status = RawChannelIBufferIsFull(Channel, &IsFull);
if (!NT_SUCCESS(Status)) return Status; if (!NT_SUCCESS(Status)) return Status;
if (IsFull) return STATUS_UNSUCCESSFUL; if (IsFull) return STATUS_UNSUCCESSFUL;
/* Get the current buffer index */
Index = RawChannelGetIBufferIndex(Channel); Index = RawChannelGetIBufferIndex(Channel);
if ((SAC_RAW_IBUFFER_SIZE - Index) >= BufferSize) return STATUS_INSUFFICIENT_RESOURCES; if ((SAC_RAW_IBUFFER_SIZE - Index) < BufferSize)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
/* Copy the new data */
RtlCopyMemory(&Channel->IBuffer[Index], Buffer, BufferSize); RtlCopyMemory(&Channel->IBuffer[Index], Buffer, BufferSize);
/* Update the index */
RawChannelSetIBufferIndex(Channel, BufferSize + Index); RawChannelSetIBufferIndex(Channel, BufferSize + Index);
/* Signal the event, if one was set */
if (Channel->Flags & SAC_CHANNEL_FLAG_HAS_NEW_DATA_EVENT) if (Channel->Flags & SAC_CHANNEL_FLAG_HAS_NEW_DATA_EVENT)
{ {
ChannelSetEvent(Channel, HasNewDataEvent); ChannelSetEvent(Channel, HasNewDataEvent);
} }
/* All done */
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }

View file

@ -157,6 +157,7 @@
#define SAC_SERIAL_PORT_BUFFER_SIZE 1024 // 1KB #define SAC_SERIAL_PORT_BUFFER_SIZE 1024 // 1KB
#define SAC_MAX_MESSAGES 200 #define SAC_MAX_MESSAGES 200
#define SAC_VTUTF8_COL_WIDTH 80 #define SAC_VTUTF8_COL_WIDTH 80
#define SAC_VTUTF8_COL_HEIGHT 25
// //
// Channel flags // Channel flags
@ -302,7 +303,7 @@ ULONG
); );
typedef typedef
CHAR WCHAR
(NTAPI *PSAC_CHANNEL_IREAD_LAST)( (NTAPI *PSAC_CHANNEL_IREAD_LAST)(
IN struct _SAC_CHANNEL* Channel IN struct _SAC_CHANNEL* Channel
); );
@ -775,7 +776,7 @@ ChannelIWrite(
IN ULONG BufferSize IN ULONG BufferSize
); );
UCHAR WCHAR
NTAPI NTAPI
ChannelIReadLast( ChannelIReadLast(
IN PSAC_CHANNEL Channel IN PSAC_CHANNEL Channel
@ -864,7 +865,7 @@ RawChannelIBufferLength(
IN PSAC_CHANNEL Channel IN PSAC_CHANNEL Channel
); );
CHAR WCHAR
NTAPI NTAPI
RawChannelIReadLast( RawChannelIReadLast(
IN PSAC_CHANNEL Channel IN PSAC_CHANNEL Channel
@ -878,9 +879,106 @@ RawChannelIWrite(
IN ULONG BufferSize IN ULONG BufferSize
); );
//
// VT-UTF8 Channel Table
//
NTSTATUS
NTAPI
VTUTF8ChannelCreate(
IN PSAC_CHANNEL Channel
);
NTSTATUS
NTAPI
VTUTF8ChannelDestroy(
IN PSAC_CHANNEL Channel
);
NTSTATUS
NTAPI
VTUTF8ChannelORead(
IN PSAC_CHANNEL Channel,
IN PCHAR Buffer,
IN ULONG BufferSize,
OUT PULONG ByteCount
);
NTSTATUS
NTAPI
VTUTF8ChannelOEcho(
IN PSAC_CHANNEL Channel,
IN PCHAR String,
IN ULONG Length
);
NTSTATUS
NTAPI
VTUTF8ChannelOFlush(
IN PSAC_CHANNEL Channel
);
NTSTATUS
NTAPI
VTUTF8ChannelOWrite(
IN PSAC_CHANNEL Channel,
IN PCHAR String,
IN ULONG Length
);
NTSTATUS
NTAPI
VTUTF8ChannelIRead(
IN PSAC_CHANNEL Channel,
IN PCHAR Buffer,
IN ULONG BufferSize,
IN PULONG ReturnBufferSize
);
NTSTATUS
NTAPI
VTUTF8ChannelIBufferIsFull(
IN PSAC_CHANNEL Channel,
OUT PBOOLEAN BufferStatus
);
ULONG
NTAPI
VTUTF8ChannelIBufferLength(
IN PSAC_CHANNEL Channel
);
WCHAR
NTAPI
VTUTF8ChannelIReadLast(
IN PSAC_CHANNEL Channel
);
NTSTATUS
NTAPI
VTUTF8ChannelIWrite(
IN PSAC_CHANNEL Channel,
IN PCHAR Buffer,
IN ULONG BufferSize
);
// //
// Helper Routines // Helper Routines
// //
BOOLEAN
NTAPI
SacTranslateUtf8ToUnicode(
IN CHAR Utf8Char,
IN PCHAR Utf8Buffer,
OUT PWCHAR Utf8Value
);
ULONG
NTAPI
GetMessageLineCount(
IN ULONG MessageIndex
);
NTSTATUS NTSTATUS
NTAPI NTAPI
SerialBufferGetChar( SerialBufferGetChar(
@ -923,6 +1021,102 @@ DoRebootCommand(
IN BOOLEAN Reboot IN BOOLEAN Reboot
); );
VOID
NTAPI
DoFullInfoCommand(
VOID
);
VOID
NTAPI
DoPagingCommand(
VOID
);
VOID
NTAPI
DoSetTimeCommand(
IN PCHAR InputTime
);
VOID
NTAPI
DoKillCommand(
IN PCHAR KillString
);
VOID
NTAPI
DoLowerPriorityCommand(
IN PCHAR PrioString
);
VOID
NTAPI
DoRaisePriorityCommand(
IN PCHAR PrioString
);
VOID
NTAPI
DoLimitMemoryCommand(
IN PCHAR LimitString
);
VOID
NTAPI
DoCrashCommand(
VOID
);
VOID
NTAPI
DoMachineInformationCommand(
VOID
);
VOID
NTAPI
DoChannelCommand(
IN PCHAR ChannelString
);
VOID
NTAPI
DoCmdCommand(
IN PCHAR InputString
);
VOID
NTAPI
DoLockCommand(
VOID
);
VOID
NTAPI
DoHelpCommand(
VOID
);
VOID
NTAPI
DoGetNetInfo(
IN BOOLEAN DoPrint
);
VOID
NTAPI
DoSetIpAddressCommand(
IN PCHAR IpString
);
VOID
NTAPI
DoTlistCommand(
VOID
);
// //
// External data // External data
// //
@ -933,7 +1127,9 @@ extern LONG CurrentChannelRefCount;
extern PCHAR SerialPortBuffer; extern PCHAR SerialPortBuffer;
extern LONG SerialPortConsumerIndex, SerialPortProducerIndex; extern LONG SerialPortConsumerIndex, SerialPortProducerIndex;
extern PCHAR Utf8ConversionBuffer; extern PCHAR Utf8ConversionBuffer;
extern BOOLEAN GlobalPagingNeeded;
extern ULONG Utf8ConversionBufferSize; extern ULONG Utf8ConversionBufferSize;
extern BOOLEAN CommandConsoleLaunchingEnabled;
// //
// Function to initailize a SAC Semaphore Lock // Function to initailize a SAC Semaphore Lock
@ -1046,3 +1242,11 @@ ChannelGetIndex(IN PSAC_CHANNEL Channel)
/* Return the index of the channel */ /* Return the index of the channel */
return Channel->Index; return Channel->Index;
} }
FORCEINLINE
BOOLEAN
ChannelHasNewIBufferData(IN PSAC_CHANNEL Channel)
{
/* Return if there's any new data in the input buffer */
return Channel->ChannelHasNewIBufferData;
}

View file

@ -34,6 +34,56 @@ PCHAR SerialPortBuffer;
/* FUNCTIONS *****************************************************************/ /* FUNCTIONS *****************************************************************/
BOOLEAN
NTAPI
SacTranslateUtf8ToUnicode(IN CHAR Utf8Char,
IN PCHAR Utf8Buffer,
OUT PWCHAR Utf8Value)
{
ULONG i;
/* Find out how many valid characters we have in the buffer */
i = 0;
while (Utf8Buffer[i++] && (i < 3));
/* If we have at least 3, shift everything by a byte */
if (i >= 3)
{
/* The last input character goes at the end */
Utf8Buffer[0] = Utf8Buffer[1];
Utf8Buffer[1] = Utf8Buffer[2];
Utf8Buffer[2] = Utf8Char;
}
else
{
/* We don't have more than 3 characters, place the input at the index */
Utf8Buffer[i] = Utf8Char;
}
/* Print to debugger */
SAC_DBG(SAC_DBG_ENTRY_EXIT, "SacTranslateUtf8ToUnicode - About to decode the UTF8 buffer.\n");
SAC_DBG(SAC_DBG_ENTRY_EXIT, " UTF8[0]: 0x%02lx UTF8[1]: 0x%02lx UTF8[2]: 0x%02lx\n",
Utf8Buffer[0],
Utf8Buffer[1],
Utf8Buffer[2]);
/* Is this a simple ANSI character? */
if (!(Utf8Char & 0x80))
{
/* Return it as Unicode, nothing left to do */
SAC_DBG(SAC_DBG_ENTRY_EXIT, "SACDRV: SacTranslateUTf8ToUnicode - Case1\n");
*Utf8Value = (WCHAR)Utf8Char;
Utf8Buffer[0] = Utf8Buffer[1];
Utf8Buffer[1] = Utf8Buffer[2];
Utf8Buffer[2] = UNICODE_NULL;
return TRUE;
}
/* Anything else is not yet supported */
ASSERT(FALSE);
return FALSE;
}
BOOLEAN BOOLEAN
NTAPI NTAPI
SacTranslateUnicodeToUtf8(IN PWCHAR SourceBuffer, SacTranslateUnicodeToUtf8(IN PWCHAR SourceBuffer,
@ -151,8 +201,76 @@ SacFormatMessage(IN PWCHAR FormattedString,
IN PWCHAR MessageString, IN PWCHAR MessageString,
IN ULONG MessageSize) IN ULONG MessageSize)
{ {
/* FIXME: For now don't format anything */ SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SacFormatMessage: Entering.\n");
wcsncpy(FormattedString, MessageString, MessageSize / sizeof(WCHAR));
/* Check if any of the parameters are NULL or zero */
if (!(MessageString) || !(FormattedString) || !(MessageSize))
{
SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SacFormatMessage: Exiting with invalid parameters.\n");
return;
}
/* Keep going as long as there's still characters */
while ((MessageString[0]) && (MessageSize))
{
/* Is it a non-formatting character? */
if (MessageString[0] != L'%')
{
/* Just write it back into the buffer and keep going */
*FormattedString++ = MessageString[0];
MessageString++;
}
else
{
/* Go over the format characters we recognize */
switch (MessageString[1])
{
case L'0':
*FormattedString = UNICODE_NULL;
return;
case L'%':
*FormattedString++ = L'%';
break;
case L'\\':
*FormattedString++ = L'\r';
*FormattedString++ = L'\n';
break;
case L'r':
*FormattedString++ = L'\r';
break;
case L'b':
*FormattedString++ = L' ';
break;
case L'.':
*FormattedString++ = L'.';
break;
case L'!':
*FormattedString++ = L'!';
break;
default:
/* Only move forward one character */
MessageString--;
break;
}
/* Move forward two characters */
MessageString += 2;
}
/* Move to the next character*/
MessageSize--;
}
/* All done */
*FormattedString = UNICODE_NULL;
SAC_DBG(SAC_DBG_ENTRY_EXIT, "SAC SacFormatMessage: Exiting.\n");
} }
NTSTATUS NTSTATUS
@ -1105,6 +1223,25 @@ SerialBufferGetChar(OUT PCHAR Char)
return STATUS_SUCCESS; return STATUS_SUCCESS;
} }
ULONG
NTAPI
GetMessageLineCount(IN ULONG MessageIndex)
{
ULONG LineCount = 0;
PWCHAR Buffer;
/* Get the message buffer */
Buffer = GetMessage(MessageIndex);
if (Buffer)
{
/* Scan it looking for new lines, and increment the conut each time */
while (*Buffer) if (*Buffer++ == L'\n') ++LineCount;
}
/* Return the line count */
return LineCount;
}
ULONG ULONG
ConvertAnsiToUnicode( ConvertAnsiToUnicode(
IN PWCHAR pwch, IN PWCHAR pwch,
@ -1131,16 +1268,6 @@ InvokeUserModeService(
return STATUS_NOT_IMPLEMENTED; return STATUS_NOT_IMPLEMENTED;
} }
BOOLEAN
SacTranslateUtf8ToUnicode(
IN CHAR Utf8Char,
IN PCHAR UnicodeBuffer,
OUT PCHAR Utf8Value
)
{
return FALSE;
}
NTSTATUS NTSTATUS
TranslateMachineInformationText( TranslateMachineInformationText(
IN PWCHAR Buffer) IN PWCHAR Buffer)
@ -1159,14 +1286,6 @@ CopyAndInsertStringAtInterval(
return STATUS_NOT_IMPLEMENTED; return STATUS_NOT_IMPLEMENTED;
} }
ULONG
GetMessageLineCount(
IN ULONG MessageIndex
)
{
return 0;
}
NTSTATUS NTSTATUS
RegisterSacCmdEvent( RegisterSacCmdEvent(
IN PVOID Object, IN PVOID Object,

View file

@ -1,36 +1,109 @@
/* /*
* PROJECT: ReactOS Boot Loader * PROJECT: ReactOS Drivers
* LICENSE: BSD - See COPYING.ARM in the top level directory * LICENSE: BSD - See COPYING.ARM in the top level directory
* FILE: drivers/sac/driver/vtutf8chan.c * FILE: drivers/sac/driver/vtutf8chan.c
* PURPOSE: Driver for the Server Administration Console (SAC) for EMS * PURPOSE: Driver for the Server Administration Console (SAC) for EMS
* PROGRAMMERS: ReactOS Portable Systems Group * PROGRAMMERS: ReactOS Portable Systems Group
*/ */
/* INCLUDES *******************************************************************/ /* INCLUDES ******************************************************************/
#include "sacdrv.h" #include "sacdrv.h"
/* GLOBALS ********************************************************************/ /* GLOBALS *******************************************************************/
/* FUNCTIONS ******************************************************************/ CHAR IncomingUtf8ConversionBuffer[4];
WCHAR IncomingUnicodeValue;
/* FUNCTIONS *****************************************************************/
typedef struct _SAC_CURSOR_DATA
{
UCHAR CursorX;
UCHAR CursorY;
UCHAR CursorVisible;
WCHAR CursorValue;
} SAC_CURSOR_DATA, *PSAC_CURSOR_DATA;
C_ASSERT(sizeof(SAC_CURSOR_DATA) == 6);
#define SAC_VTUTF8_OBUFFER_SIZE 0x2D00
#define SAC_VTUTF8_IBUFFER_SIZE 0x2000
NTSTATUS NTSTATUS
VTUTF8ChannelCreate( NTAPI
IN PSAC_CHANNEL Channel VTUTF8ChannelOInit(IN PSAC_CHANNEL Channel)
)
{ {
return STATUS_NOT_IMPLEMENTED; PSAC_CURSOR_DATA Cursor;
ULONG x, y;
CHECK_PARAMETER(Channel);
/* Set the current channel cursor parameters */
Channel->CursorVisible = 0;
Channel->CursorX = 40;
Channel->CursorY = 37;
/* Loop the output buffer height by width */
Cursor = (PSAC_CURSOR_DATA)Channel->OBuffer;
y = SAC_VTUTF8_COL_HEIGHT - 1;
do
{
x = SAC_VTUTF8_COL_WIDTH;
do
{
/* For every character, set the defaults */
Cursor->CursorValue = ' ';
Cursor->CursorX = 40;
Cursor->CursorY = 38;
/* Move to the next character */
Cursor++;
} while (--x);
} while (--y);
/* All done */
return STATUS_SUCCESS;
} }
NTSTATUS NTSTATUS
VTUTF8ChannelDestroy( NTAPI
IN PSAC_CHANNEL Channel VTUTF8ChannelCreate(IN PSAC_CHANNEL Channel)
)
{ {
return STATUS_NOT_IMPLEMENTED; NTSTATUS Status;
CHECK_PARAMETER(Channel);
/* Allocate the output buffer */
Channel->OBuffer = SacAllocatePool(SAC_VTUTF8_OBUFFER_SIZE, GLOBAL_BLOCK_TAG);
CHECK_ALLOCATION(Channel->OBuffer);
/* Allocate the input buffer */
Channel->IBuffer = SacAllocatePool(SAC_VTUTF8_IBUFFER_SIZE, GLOBAL_BLOCK_TAG);
CHECK_ALLOCATION(Channel->IBuffer);
/* Initialize the output stream */
Status = VTUTF8ChannelOInit(Channel);
if (NT_SUCCESS(Status)) return Status;
/* Reset all flags and return success */
_InterlockedExchange(&Channel->ChannelHasNewOBufferData, 0);
_InterlockedExchange(&Channel->ChannelHasNewIBufferData, 0);
return STATUS_SUCCESS;
} }
NTSTATUS NTSTATUS
NTAPI
VTUTF8ChannelDestroy(IN PSAC_CHANNEL Channel)
{
CHECK_PARAMETER(Channel);
/* Free the buffer and then destroy the channel */
if (Channel->OBuffer) SacFreePool(Channel->OBuffer);
if (Channel->IBuffer) SacFreePool(Channel->IBuffer);
return ChannelDestroy(Channel);
}
NTSTATUS
NTAPI
VTUTF8ChannelORead( VTUTF8ChannelORead(
IN PSAC_CHANNEL Channel, IN PSAC_CHANNEL Channel,
IN PCHAR Buffer, IN PCHAR Buffer,
@ -41,17 +114,8 @@ VTUTF8ChannelORead(
return STATUS_NOT_IMPLEMENTED; return STATUS_NOT_IMPLEMENTED;
} }
NTSTATUS
VTUTF8ChannelOEcho(
IN PSAC_CHANNEL Channel,
IN PWCHAR String,
IN ULONG Size
)
{
return STATUS_NOT_IMPLEMENTED;
}
BOOLEAN BOOLEAN
NTAPI
VTUTF8ChannelScanForNumber( VTUTF8ChannelScanForNumber(
IN PWCHAR String, IN PWCHAR String,
OUT PULONG Number OUT PULONG Number
@ -61,6 +125,7 @@ VTUTF8ChannelScanForNumber(
} }
NTSTATUS NTSTATUS
NTAPI
VTUTF8ChannelAnsiDispatch( VTUTF8ChannelAnsiDispatch(
IN NTSTATUS Status, IN NTSTATUS Status,
IN ULONG AnsiCode, IN ULONG AnsiCode,
@ -72,6 +137,7 @@ VTUTF8ChannelAnsiDispatch(
} }
NTSTATUS NTSTATUS
NTAPI
VTUTF8ChannelProcessAttributes( VTUTF8ChannelProcessAttributes(
IN PSAC_CHANNEL Channel, IN PSAC_CHANNEL Channel,
IN UCHAR Attribute IN UCHAR Attribute
@ -80,24 +146,8 @@ VTUTF8ChannelProcessAttributes(
return STATUS_NOT_IMPLEMENTED; return STATUS_NOT_IMPLEMENTED;
} }
ULONG
VTUTF8ChannelGetIBufferIndex(
IN PSAC_CHANNEL Channel
)
{
return 0;
}
VOID
VTUTF8ChannelSetIBufferIndex(
IN PSAC_CHANNEL Channel,
IN ULONG BufferIndex
)
{
}
NTSTATUS NTSTATUS
NTAPI
VTUTF8ChannelConsumeEscapeSequence( VTUTF8ChannelConsumeEscapeSequence(
IN PSAC_CHANNEL Channel, IN PSAC_CHANNEL Channel,
IN PWCHAR String IN PWCHAR String
@ -107,6 +157,7 @@ VTUTF8ChannelConsumeEscapeSequence(
} }
NTSTATUS NTSTATUS
NTAPI
VTUTF8ChannelOFlush( VTUTF8ChannelOFlush(
IN PSAC_CHANNEL Channel IN PSAC_CHANNEL Channel
) )
@ -115,67 +166,240 @@ VTUTF8ChannelOFlush(
} }
NTSTATUS NTSTATUS
VTUTF8ChannelIRead( NTAPI
IN PSAC_CHANNEL Channel, VTUTF8ChannelOWrite2(IN PSAC_CHANNEL Channel,
IN PCHAR Buffer, IN PCHAR String,
IN ULONG BufferSize, IN ULONG Size)
IN PULONG ReturnBufferSize
)
{ {
return STATUS_NOT_IMPLEMENTED; return STATUS_NOT_IMPLEMENTED;
} }
NTSTATUS NTSTATUS
VTUTF8ChannelIBufferIsFull( NTAPI
IN PSAC_CHANNEL Channel, VTUTF8ChannelOEcho(IN PSAC_CHANNEL Channel,
OUT PBOOLEAN BufferStatus IN PCHAR String,
) IN ULONG Size)
{ {
return STATUS_NOT_IMPLEMENTED; return STATUS_NOT_IMPLEMENTED;
} }
WCHAR NTSTATUS
VTUTF8ChannelIReadLast( NTAPI
IN PSAC_CHANNEL Channel VTUTF8ChannelOWrite(IN PSAC_CHANNEL Channel,
) IN PCHAR String,
IN ULONG Length)
{ {
return 0; NTSTATUS Status;
CHECK_PARAMETER1(Channel);
CHECK_PARAMETER2(String);
/* Call the lower level function */
Status = VTUTF8ChannelOWrite2(Channel, String, Length / sizeof(WCHAR));
if (NT_SUCCESS(Status))
{
/* Is the channel enabled for output? */
if ((ConMgrIsWriteEnabled(Channel)) && (Channel->WriteEnabled))
{
/* Go ahead and output it */
Status = VTUTF8ChannelOEcho(Channel, String, Length);
}
else
{
/* Otherwise, just remember that we have new data */
_InterlockedExchange(&Channel->ChannelHasNewOBufferData, 1);
}
}
/* We're done */
return Status;
} }
ULONG ULONG
VTUTF8ChannelIBufferLength( NTAPI
IN PSAC_CHANNEL Channel VTUTF8ChannelGetIBufferIndex(IN PSAC_CHANNEL Channel)
)
{ {
return 0; ASSERT(Channel);
ASSERT((Channel->IBufferIndex % sizeof(WCHAR)) == 0);
ASSERT(Channel->IBufferIndex < SAC_VTUTF8_IBUFFER_SIZE);
/* Return the current buffer index */
return Channel->IBufferIndex;
}
VOID
NTAPI
VTUTF8ChannelSetIBufferIndex(IN PSAC_CHANNEL Channel,
IN ULONG BufferIndex)
{
NTSTATUS Status;
ASSERT(Channel);
ASSERT((Channel->IBufferIndex % sizeof(WCHAR)) == 0);
ASSERT(Channel->IBufferIndex < SAC_VTUTF8_IBUFFER_SIZE);
/* Set the new index, and if it's not zero, it means we have data */
Channel->IBufferIndex = BufferIndex;
_InterlockedExchange(&Channel->ChannelHasNewIBufferData, BufferIndex != 0);
/* If we have new data, and an event has been registered... */
if (!(Channel->IBufferIndex) &&
(Channel->Flags & SAC_CHANNEL_FLAG_HAS_NEW_DATA_EVENT))
{
/* Go ahead and signal it */
ChannelClearEvent(Channel, HasNewDataEvent);
UNREFERENCED_PARAMETER(Status);
}
} }
NTSTATUS NTSTATUS
VTUTF8ChannelOWrite2( NTAPI
IN PSAC_CHANNEL Channel, VTUTF8ChannelIRead(IN PSAC_CHANNEL Channel,
IN PWCHAR String, IN PCHAR Buffer,
IN ULONG Size IN ULONG BufferSize,
) IN PULONG ReturnBufferSize)
{ {
return STATUS_NOT_IMPLEMENTED; ULONG CopyChars;
CHECK_PARAMETER1(Channel);
CHECK_PARAMETER2(Buffer);
CHECK_PARAMETER_WITH_STATUS(BufferSize > 0, STATUS_INVALID_BUFFER_SIZE);
/* Assume failure */
*ReturnBufferSize = 0;
/* Check how many bytes are in the buffer */
if (Channel->ChannelInputBufferLength(Channel) == 0)
{
/* Apparently nothing. Make sure the flag indicates so too */
ASSERT(ChannelHasNewIBufferData(Channel) == FALSE);
}
else
{
/* Use the smallest number of bytes either in the buffer or requested */
CopyChars = min(Channel->ChannelInputBufferLength(Channel) * sizeof(WCHAR),
BufferSize);
ASSERT(CopyChars <= Channel->ChannelInputBufferLength(Channel));
/* Copy them into the caller's buffer */
RtlCopyMemory(Buffer, Channel->IBuffer, CopyChars);
/* Update the channel's index past the copied (read) bytes */
VTUTF8ChannelSetIBufferIndex(Channel,
VTUTF8ChannelGetIBufferIndex(Channel) - CopyChars);
/* Are there still bytes that haven't been read yet? */
if (Channel->ChannelInputBufferLength(Channel))
{
/* Shift them up in the buffer */
RtlMoveMemory(Channel->IBuffer,
&Channel->IBuffer[CopyChars],
Channel->ChannelInputBufferLength(Channel) *
sizeof(WCHAR));
}
/* Return the number of bytes we actually copied */
*ReturnBufferSize = CopyChars;
}
/* Return success */
return STATUS_SUCCESS;
} }
NTSTATUS NTSTATUS
VTUTF8ChannelIWrite( NTAPI
IN PSAC_CHANNEL Channel, VTUTF8ChannelIBufferIsFull(IN PSAC_CHANNEL Channel,
IN PCHAR Buffer, OUT PBOOLEAN BufferStatus)
IN ULONG BufferSize
)
{ {
return STATUS_NOT_IMPLEMENTED; CHECK_PARAMETER1(Channel);
/* If the index is beyond the length, the buffer must be full */
*BufferStatus = VTUTF8ChannelGetIBufferIndex(Channel) > SAC_VTUTF8_IBUFFER_SIZE;
return STATUS_SUCCESS;
}
ULONG
NTAPI
VTUTF8ChannelIBufferLength(IN PSAC_CHANNEL Channel)
{
ASSERT(Channel);
/* The index is the length, so divide by two to get character count */
return VTUTF8ChannelGetIBufferIndex(Channel) / sizeof(WCHAR);
}
WCHAR
NTAPI
VTUTF8ChannelIReadLast(IN PSAC_CHANNEL Channel)
{
PWCHAR LastCharLocation;
WCHAR LastChar = 0;
ASSERT(Channel);
/* Check if there's anything to read in the buffer */
if (Channel->ChannelInputBufferLength(Channel))
{
/* Go back one character */
VTUTF8ChannelSetIBufferIndex(Channel,
VTUTF8ChannelGetIBufferIndex(Channel) -
sizeof(WCHAR));
/* Read it, and clear its current value */
LastCharLocation = (PWCHAR)&Channel->IBuffer[VTUTF8ChannelGetIBufferIndex(Channel)];
LastChar = *LastCharLocation;
*LastCharLocation = UNICODE_NULL;
}
/* Return the last character */
return LastChar;
} }
NTSTATUS NTSTATUS
VTUTF8ChannelOWrite( NTAPI
IN PSAC_CHANNEL Channel, VTUTF8ChannelIWrite(IN PSAC_CHANNEL Channel,
IN PWCHAR String, IN PCHAR Buffer,
IN ULONG Length IN ULONG BufferSize)
)
{ {
return STATUS_NOT_IMPLEMENTED; NTSTATUS Status;
BOOLEAN IsFull;
ULONG Index, i;
CHECK_PARAMETER1(Channel);
CHECK_PARAMETER2(Buffer);
CHECK_PARAMETER_WITH_STATUS(BufferSize > 0, STATUS_INVALID_BUFFER_SIZE);
/* First, check if the input buffer still has space */
Status = VTUTF8ChannelIBufferIsFull(Channel, &IsFull);
if (!NT_SUCCESS(Status)) return Status;
if (IsFull) return STATUS_UNSUCCESSFUL;
/* Get the current buffer index */
Index = VTUTF8ChannelGetIBufferIndex(Channel);
if ((SAC_VTUTF8_IBUFFER_SIZE - Index) < BufferSize)
{
return STATUS_INSUFFICIENT_RESOURCES;
}
/* Copy the new data */
for (i = 0; i < BufferSize; i++)
{
/* Convert the character */
if (SacTranslateUtf8ToUnicode(Buffer[i],
IncomingUtf8ConversionBuffer,
&IncomingUnicodeValue))
{
/* Write it into the buffer */
*(PWCHAR)&Channel->IBuffer[VTUTF8ChannelGetIBufferIndex(Channel)] =
IncomingUnicodeValue;
/* Update the index */
Index = VTUTF8ChannelGetIBufferIndex(Channel);
VTUTF8ChannelSetIBufferIndex(Channel, Index + sizeof(WCHAR));
}
}
/* Signal the event, if one was set */
if (Channel->Flags & SAC_CHANNEL_FLAG_HAS_NEW_DATA_EVENT)
{
ChannelSetEvent(Channel, HasNewDataEvent);
}
/* All done */
return STATUS_SUCCESS;
} }

View file

@ -1,7 +1,12 @@
MessageId=1 MessageId=1
SymbolicName=SAC_INIT_STATUS SymbolicName=SAC_INIT_STATUS
Language=English Language=English
Computer is booting, SAC started and initialized.\n\nUse the \"ch -?\" command for information about using channels.\nUse the \"?\" command for general help.
Computer is booting, SAC started and initialized.
Use the "ch -?" command for information about using channels.
Use the "?" command for general help.
. .
MessageId=2 MessageId=2
@ -25,7 +30,8 @@ The SAC is unavailable, it was directly unloaded.
MessageId=5 MessageId=5
SymbolicName=SACDRV_5 SymbolicName=SACDRV_5
Language=English Language=English
The SAC will become unavailable soon. The computer is shutting down.\n The SAC will become unavailable soon. The computer is shutting down.
. .
MessageId=6 MessageId=6
@ -295,7 +301,9 @@ SAC failed to retrieve the task list.
MessageId=69 MessageId=69
SymbolicName=SACDRV_69 SymbolicName=SACDRV_69
Language=English Language=English
memory: %%4ld kb uptime:%%3ld %%2ld:%%02ld:%%02ld.%%03ld\n\n memory: %%4ld kb uptime:%%3ld %%2ld:%%02ld:%%02ld.%%03ld
. .
MessageId=70 MessageId=70
@ -319,7 +327,8 @@ Language=English
MessageId=73 MessageId=73
SymbolicName=SACDRV_73 SymbolicName=SACDRV_73
Language=English Language=English
\n Memory:%%7ldK Avail:%%7ldK TotalWs:%%7ldK InRam Kernel:%%5ldK P:%%5ldK
Memory:%%7ldK Avail:%%7ldK TotalWs:%%7ldK InRam Kernel:%%5ldK P:%%5ldK
. .
MessageId=74 MessageId=74
@ -421,7 +430,8 @@ That process has been killed and is being cleaned up by the system.
MessageId=90 MessageId=90
SymbolicName=SACDRV_90 SymbolicName=SACDRV_90
Language=English Language=English
A duplicate process id is being cleaned up by the system. Try the \ncommand again in a few seconds. A duplicate process id is being cleaned up by the system. Try the
command again in a few seconds.
. .
MessageId=92 MessageId=92
@ -523,31 +533,46 @@ Error: Could not find a channel with that name.
MessageId=108 MessageId=108
SymbolicName=SACDRV_108 SymbolicName=SACDRV_108
Language=English Language=English
Channel List\n \n(Use \"ch -?\" for information on using channels)\n\n# Status Channel Name Channel List
(Use "ch -?" for information on using channels)
# Status Channel Name
. .
MessageId=109 MessageId=109
SymbolicName=SACDRV_109 SymbolicName=SACDRV_109
Language=English Language=English
EVENT: A new channel has been created. Use \"ch -?\" for channel help.\nChannel: %%s EVENT: A new channel has been created. Use "ch -?" for channel help.
Channel: %%s
. .
MessageId=110 MessageId=110
SymbolicName=SACDRV_110 SymbolicName=SACDRV_110
Language=English Language=English
EVENT: A channel has been closed.\nChannel: %%s EVENT: A channel has been closed.
Channel: %%s
. .
MessageId=111 MessageId=111
SymbolicName=SACDRV_111 SymbolicName=SACDRV_111
Language=English Language=English
Name: %%s\nDescription: %%s\nType: %%s\nChannel GUID: %%08lx-%%04x-%%04x-%%02x%%02x-%%02x%%02x%%02x%%02x%%02x%%02x\nApplication Type GUID: %%08lx-%%04x-%%04x-%%02x%%02x-%%02x%%02x%%02x%%02x%%02x%%02x\n\nPress <esc><tab> for next channel.\nPress <esc><tab>0 to return to the SAC channel.\nUse any other key to view this channel.\n Name: %%s
Description: %%s
Type: %%s
Channel GUID: %%08lx-%%04x-%%04x-%%02x%%02x-%%02x%%02x%%02x%%02x%%02x%%02x
Application Type GUID: %%08lx-%%04x-%%04x-%%02x%%02x-%%02x%%02x%%02x%%02x%%02x%%02x
Press <esc><tab> for next channel.
Press <esc><tab>0 to return to the SAC channel.
Use any other key to view this channel.
. .
MessageId=112 MessageId=112
SymbolicName=SACDRV_112 SymbolicName=SACDRV_112
Language=English Language=English
ch Channel management commands. Use ch -? for more help. ch Channel management commands. Use ch -? for more help.
. .
MessageId=113 MessageId=113
@ -571,7 +596,10 @@ SAC preparing to shutdown the system.
MessageId=116 MessageId=116
SymbolicName=SACDRV_116 SymbolicName=SACDRV_116
Language=English Language=English
Error! Failed to remove channel! \n\nPlease contact your system administrator.\n Error! Failed to remove channel!
Please contact your system administrator.
. .
MessageId=119 MessageId=119
@ -583,7 +611,9 @@ cmd Create a Command Prompt channel.
MessageId=120 MessageId=120
SymbolicName=SACDRV_120 SymbolicName=SACDRV_120
Language=English Language=English
Timeout: Unable to launch a Command Prompt. The service responsible for \n launching Command Prompt channels has timed out. This may be \n because the service is malfunctioning or is unresponsive. Timeout: Unable to launch a Command Prompt. The service responsible for
launching Command Prompt channels has timed out. This may be
because the service is malfunctioning or is unresponsive.
. .
MessageId=121 MessageId=121
@ -601,7 +631,10 @@ Error: The SAC Command Console session failed to be created.
MessageId=131 MessageId=131
SymbolicName=SACDRV_131 SymbolicName=SACDRV_131
Language=English Language=English
Error: Unable to launch a Command Prompt. The service responsible for launching\n Command Prompt channels has not yet registered. This may be because the\n service is not yet started, is disabled by the administrator, is\n malfunctioning or is unresponsive. Error: Unable to launch a Command Prompt. The service responsible for launching
Command Prompt channels has not yet registered. This may be because the
service is not yet started, is disabled by the administrator, is
malfunctioning or is unresponsive.
. .
MessageId=132 MessageId=132
@ -619,19 +652,39 @@ EVENT: The CMD command is unavailable.
MessageId=134 MessageId=134
SymbolicName=SACDRV_134 SymbolicName=SACDRV_134
Language=English Language=English
EVENT: An attempt was made to close a channel but failed.\nChannel: %%s EVENT: An attempt was made to close a channel but failed.
Channel: %%s
. .
MessageId=135 MessageId=135
SymbolicName=SACDRV_135 SymbolicName=SACDRV_135
Language=English Language=English
EVENT: An attempt to close a channel failed because it is already closed.\nChannel: %%s EVENT: An attempt to close a channel failed because it is already closed.
Channel: %%s
. .
MessageId=136 MessageId=136
SymbolicName=SACDRV_136 SymbolicName=SACDRV_136
Language=English Language=English
Channel management commands:\n\nch List all channels.\n\nStatus Legend: (AB)\nA: Channel operational status\n 'A' = Channel is active.\n 'I' = Channel is inactive.\nB: Channel Type\n 'V' = VT-UTF8 emulation.\n 'R' = Raw - no emulation.\n\nch -si <#> Switch to a channel by its number.\nch -sn <name> Switch to a channel by its name.\nch -ci <#> Close a channel by its number.\nch -cn <name> Close a channel by its name.\n\nPress <esc><tab> to select a channel.\nPress <esc><tab>0 to return to the SAC channel. Channel management commands:
ch List all channels.
Status Legend: (AB)
A: Channel operational status
'A' = Channel is active.
'I' = Channel is inactive.
B: Channel Type
'V' = VT-UTF8 emulation.
'R' = Raw - no emulation.
ch -si <#> Switch to a channel by its number.
ch -sn <name> Switch to a channel by its name.
ch -ci <#> Close a channel by its number.
ch -cn <name> Close a channel by its name.
Press <esc><tab> to select a channel.
Press <esc><tab>0 to return to the SAC channel.
. .
MessageId=137 MessageId=137
@ -703,5 +756,5 @@ not yet initialized%0
MessageId=154 MessageId=154
SymbolicName=SACDRV_154 SymbolicName=SACDRV_154
Language=English Language=English
The maximum number of channels has been reached. The maximum number of channels has been reached.
. .