MODULE BootShell;
IMPORT
SYSTEM, KernelLog, Machine, Modules, Streams, Commands, Inputs, Strings, Locks;
CONST
Version = "A2 Bootshell v1.0";
LineWidth = 80; TraceHeight = 25;
TraceBase = 0B8000H;
BufferHeight = 2048;
BufferSize = BufferHeight * LineWidth;
TAB = 09X;
CR = 0DX;
LF = 0AX;
SPACE = " ";
Mode_Insert = 0;
Mode_Overwrite = 1;
Black = 0;
Blue = 1;
Green = 2;
Cyan = 3;
Red = 4;
Magenta = 5;
Brown = 6;
White = 7;
Gray = 8;
LightBlue = 9;
LightGreen = 10;
LightCyan = 11;
LightRed = 12;
LightMagenta = 13;
Yellow = 14;
BrightWhite = 15;
TYPE
CommandsString = POINTER TO RECORD
prev, next: CommandsString;
string: Strings.String;
END;
CommandHistoryObject = OBJECT
VAR
first, current: CommandsString;
PROCEDURE GetNextCommand() : Strings.String;
VAR string : Strings.String;
BEGIN
IF first # NIL THEN
IF current = NIL THEN current := first ELSE current := current.next END;
string := current.string;
ELSE
string := NIL;
END;
RETURN string;
END GetNextCommand;
PROCEDURE GetPreviousCommand() : Strings.String;
VAR string : Strings.String;
BEGIN
IF first # NIL THEN
IF current = NIL THEN current := first.prev ELSE current := current.prev END;
string := current.string;
ELSE
string := NIL;
END;
RETURN string;
END GetPreviousCommand;
PROCEDURE AddCommand(string : Strings.String);
VAR command: CommandsString;
BEGIN
ASSERT((string # NIL) & (string^ # ""));
command := first;
IF command # NIL THEN
WHILE (command.string^ # string^) & (command.next # first) DO command := command.next END;
IF command.string^ # string^ THEN command := NIL END
END;
IF command # NIL THEN
IF first = command THEN first := command.next END;
command.prev.next := command.next;
command.next.prev := command.prev;
ELSE
NEW (command);
command.string := string;
END;
IF first = NIL THEN
first := command; first.next := first; first.prev := first
ELSE
command.prev := first.prev; command.next := first;
first.prev.next := command; first.prev := command;
END;
current := NIL;
END AddCommand;
PROCEDURE &Init*;
BEGIN first := NIL; current := NIL;
END Init;
END CommandHistoryObject;
TYPE
Character = RECORD
ch : CHAR;
color : SHORTINT;
END;
Line = ARRAY LineWidth OF Character;
TextBuffer = OBJECT
VAR
defaultColor : SHORTINT;
currentColor : SHORTINT;
lines : ARRAY BufferHeight OF Line;
firstLine, lastLine : LONGINT;
firstLineShown : LONGINT;
editStartPosition, editEndPosition : LONGINT;
cursorPosition : LONGINT;
mode : LONGINT;
lock : Locks.RecursiveLock;
PROCEDURE &Init*;
BEGIN
mode := Mode_Insert;
NEW(lock);
lock.Acquire;
Clear;
lock.Release;
END Init;
PROCEDURE Clear;
VAR i : LONGINT;
BEGIN
ASSERT(lock.HasLock());
firstLine := 0; lastLine := 0;
firstLineShown := 0;
cursorPosition := 0;
editStartPosition := 0; editEndPosition := 0;
SetColor(White, Black);
defaultColor := White + 10H * Black;
FOR i := 0 TO LEN(lines)-1 DO
ClearLine(lines[i], 0, LineWidth-1, defaultColor);
END;
Invalidate(SELF);
END Clear;
PROCEDURE SetColor(foreground, background : SHORTINT);
BEGIN
currentColor := foreground + 10H * background;
END SetColor;
PROCEDURE SetEditStart;
BEGIN
editStartPosition := cursorPosition;
editEndPosition := cursorPosition;
END SetEditStart;
PROCEDURE Send( CONST data: ARRAY OF CHAR; ofs, len: LONGINT; propagate: BOOLEAN; VAR res: LONGINT);
VAR i : LONGINT;
BEGIN
lock.Acquire;
FOR i := ofs TO ofs + len - 1 DO
CharInternal(data[i]);
END;
CheckVisibility;
Invalidate(SELF);
lock.Release;
res := Streams.Ok;
END Send;
PROCEDURE String(CONST string : ARRAY OF CHAR);
VAR i : LONGINT;
BEGIN
lock.Acquire;
i := 0;
WHILE (i < LEN(string)) & (string[i] # 0X) DO
CharInternal(string[i]);
INC(i);
END;
CheckVisibility;
Invalidate(SELF);
lock.Release;
END String;
PROCEDURE Char(ch : CHAR);
BEGIN
lock.Acquire;
CharInternal(ch);
CheckVisibility;
Invalidate(SELF);
lock.Release;
END Char;
PROCEDURE CheckVisibility;
BEGIN
ASSERT(lock.HasLock());
IF (Difference(lastLine, firstLineShown, LEN(lines)) > TraceHeight - 1) THEN
firstLineShown := Subtract(lastLine, TraceHeight - 1, LEN(lines));
Invalidate(SELF);
END;
END CheckVisibility;
PROCEDURE NextLine;
BEGIN
ASSERT(lock.HasLock());
lastLine := Add(lastLine, 1, BufferHeight);
ClearLine(lines[lastLine], 0, LineWidth-1, defaultColor);
IF (lastLine = firstLine) THEN
firstLine := Add(firstLine, 1, BufferHeight);
IF (firstLineShown = lastLine) THEN
firstLineShown := firstLine;
END;
END;
END NextLine;
PROCEDURE MoveCharactersToRight;
VAR current, previous : LONGINT;
BEGIN
ASSERT(editStartPosition # editEndPosition);
IF (editEndPosition = LineWidth-1) THEN
NextLine;
END;
editEndPosition := Add(editEndPosition, 1, BufferSize);
current := editEndPosition;
WHILE (current # cursorPosition) DO
previous := Subtract(current, 1, BufferSize);
lines[current DIV LineWidth][current MOD LineWidth] := lines[previous DIV LineWidth][previous MOD LineWidth];
current := previous;
END;
END MoveCharactersToRight;
PROCEDURE MoveCharactersToLeft;
VAR current, next : LONGINT;
BEGIN
ASSERT(editStartPosition # editEndPosition);
IF (editEndPosition = 0) THEN
lastLine := Subtract(lastLine, 1, LEN(lines));
END;
current := cursorPosition;
REPEAT
next := Add(current, 1, BufferSize);
lines[current DIV LineWidth][current MOD LineWidth] := lines[next DIV LineWidth][next MOD LineWidth];
current := next;
UNTIL (next = editEndPosition);
editEndPosition := Subtract(editEndPosition, 1, BufferSize);
END MoveCharactersToLeft;
PROCEDURE CharInternal(ch : CHAR);
VAR index : LONGINT;
BEGIN
ASSERT(lock.HasLock());
IF (ch = CR) THEN
ELSIF (ch = LF) THEN
ClearLine(lines[cursorPosition DIV LineWidth], cursorPosition MOD LineWidth, LineWidth-1, currentColor);
NextLine;
cursorPosition := Add(cursorPosition, LineWidth - (cursorPosition MOD LineWidth), BufferSize);
editEndPosition := cursorPosition;
ELSIF (SPACE <= ch) & (ORD(ch) < 128) THEN
index := cursorPosition DIV LineWidth;
IF (cursorPosition = editEndPosition) THEN
ASSERT(index = lastLine);
lines[index][cursorPosition MOD LineWidth].ch := ch;
lines[index][cursorPosition MOD LineWidth].color := currentColor;
cursorPosition := Add(cursorPosition, 1, BufferSize);
editEndPosition := cursorPosition;
IF (cursorPosition DIV LineWidth # index) THEN
NextLine;
END;
ELSE
IF (mode # Mode_Overwrite) THEN
MoveCharactersToRight;
END;
lines[index][cursorPosition MOD LineWidth].ch := ch;
lines[index][cursorPosition MOD LineWidth].color := currentColor;
cursorPosition := Add(cursorPosition, 1, BufferSize);
END;
END;
END CharInternal;
PROCEDURE DeleteCurrentLine;
VAR i : LONGINT;
BEGIN
lock.Acquire;
i := editStartPosition;
LOOP
lines[i DIV LineWidth][i MOD LineWidth].ch := SPACE;
IF (i = editEndPosition) THEN EXIT; END;
INC(i);
END;
cursorPosition := editStartPosition;
editEndPosition := editStartPosition;
lastLine := editStartPosition DIV LineWidth;
lock.Release;
END DeleteCurrentLine;
PROCEDURE GetCurrentLine() : Strings.String;
VAR string : Strings.String; i, length : LONGINT;
BEGIN
lock.Acquire;
length := Difference(editEndPosition, editStartPosition, BufferSize);
NEW(string, length + 1);
i := 0;
WHILE (i < length - 1) DO
string[i] := lines[(editStartPosition + i) DIV LineWidth][(editStartPosition + i) MOD LineWidth].ch;
INC(i);
END;
string[length-1] := 0X;
lock.Release;
RETURN string;
END GetCurrentLine;
PROCEDURE Home;
BEGIN
lock.Acquire;
IF (cursorPosition # editStartPosition) THEN
cursorPosition := editStartPosition;
Invalidate(SELF);
END;
lock.Release;
END Home;
PROCEDURE End;
BEGIN
lock.Acquire;
IF (cursorPosition # editEndPosition) THEN
cursorPosition := editEndPosition;
Invalidate(SELF);
END;
lock.Release;
END End;
PROCEDURE Backspace;
BEGIN
lock.Acquire;
IF (cursorPosition # editStartPosition) THEN
cursorPosition := Subtract(cursorPosition, 1, BufferSize);
MoveCharactersToLeft;
Invalidate(SELF);
END;
lock.Release;
END Backspace;
PROCEDURE Delete;
BEGIN
lock.Acquire;
IF (cursorPosition # editEndPosition) THEN
MoveCharactersToLeft;
Invalidate(SELF);
END;
lock.Release;
END Delete;
PROCEDURE ScrollUp(nofLines : LONGINT);
VAR d : LONGINT;
BEGIN
lock.Acquire;
d := Difference(firstLineShown, firstLine, LEN(lines));
nofLines := Min(nofLines, d - 1);
IF (nofLines > 0) THEN
firstLineShown := Subtract(firstLineShown, nofLines, LEN(lines));
END;
Invalidate(SELF);
lock.Release;
END ScrollUp;
PROCEDURE ScrollDown(nofLines : LONGINT);
VAR d : LONGINT;
BEGIN
lock.Acquire;
d := Difference(lastLine, firstLineShown, LEN(lines));
nofLines := Min(nofLines, d - 1);
IF (nofLines > 0) THEN
firstLineShown := Add(firstLineShown, nofLines, LEN(lines));
END;
Invalidate(SELF);
lock.Release;
END ScrollDown;
PROCEDURE CursorLeft;
VAR oldCursorPosition : LONGINT;
BEGIN
lock.Acquire;
IF (cursorPosition # editStartPosition) THEN
oldCursorPosition := cursorPosition;
cursorPosition := Subtract(cursorPosition, 1, BufferSize);
Invalidate(SELF);
END;
lock.Release;
END CursorLeft;
PROCEDURE CursorRight;
VAR oldCursorPosition : LONGINT;
BEGIN
lock.Acquire;
IF (cursorPosition # editEndPosition) THEN
oldCursorPosition := cursorPosition;
cursorPosition := Add(cursorPosition, 1, BufferSize);
Invalidate(SELF);
END;
lock.Release;
END CursorRight;
PROCEDURE Dump(out : Streams.Writer);
VAR i, j : LONGINT;
BEGIN
ASSERT(out # NIL);
lock.Acquire;
out.String("firstLine = "); out.Int(firstLine, 0); out.String(", lastLine = "); out.Int(lastLine, 0); out.Ln;
out.String("firstLineShown = "); out.Int(firstLineShown, 0); out.Ln;
out.String("cursorPosition = "); out.Int(cursorPosition, 0); out.Ln;
out.String("editStartPosition = "); out.Int(editStartPosition, 0); out.String(", editEndPosition = "); out.Int(editEndPosition, 0); out.Ln;
i := firstLine;
LOOP
FOR j := 0 TO LineWidth-1 DO
out.Char(lines[i MOD LEN(lines)][j].ch);
END;
out.Ln;
IF (i = lastLine) THEN EXIT; END;
INC(i);
END;
out.Ln;
lock.Release;
END Dump;
END TextBuffer;
TYPE
Shell = OBJECT(Inputs.Sink)
VAR
textBuffer : TextBuffer;
history : CommandHistoryObject;
PROCEDURE &Init;
BEGIN
NEW(textBuffer);
textBuffer.lock.Acquire;
textBuffer.SetColor(Yellow, Black);
textBuffer.String(Version);
textBuffer.Char(LF);
Prompt;
textBuffer.SetEditStart;
textBuffer.lock.Release;
NEW(history);
Inputs.keyboard.Register(SELF);
END Init;
PROCEDURE Handle(VAR msg: Inputs.Message);
BEGIN
IF (msg IS Inputs.KeyboardMsg) & (msg(Inputs.KeyboardMsg).flags * {Inputs.Release} = {}) THEN
WITH msg:Inputs.KeyboardMsg DO
IF (msg.keysym = Inputs.KsPageUp) THEN
IF (msg.flags * Inputs.Shift # {}) THEN textBuffer.ScrollUp(1); ELSE textBuffer.ScrollUp(TraceHeight); END;
ELSIF (msg.keysym = Inputs.KsPageDown) THEN
IF (msg.flags * Inputs.Shift # {}) THEN textBuffer.ScrollDown(1); ELSE textBuffer.ScrollDown(TraceHeight); END;
ELSIF (msg.keysym = Inputs.KsLeft) THEN
textBuffer.CursorLeft;
ELSIF (msg.keysym = Inputs.KsRight) THEN
textBuffer.CursorRight;
ELSIF (msg.keysym = Inputs.KsUp) THEN
CommandHistory(FALSE);
ELSIF (msg.keysym = Inputs.KsDown) THEN
CommandHistory(TRUE);
ELSIF (msg.keysym = Inputs.KsHome) THEN
textBuffer.Home;
ELSIF (msg.keysym = Inputs.KsEnd) THEN
textBuffer.End;
ELSIF (msg.keysym = Inputs.KsDelete) THEN
textBuffer.Delete;
ELSIF (msg.keysym = Inputs.KsBackSpace) THEN
textBuffer.Backspace;
ELSIF (msg.keysym = Inputs.KsReturn) THEN
textBuffer.lock.Acquire;
textBuffer.cursorPosition := textBuffer.editEndPosition;
textBuffer.Char(LF);
textBuffer.lock.Release;
Execute;
textBuffer.lock.Acquire;
textBuffer.Char(LF);
Prompt;
textBuffer.SetEditStart;
textBuffer.lock.Release;
ELSIF (msg.ch = LF) OR ((SPACE <= msg.ch) & (ORD(msg.ch) < 128)) THEN
textBuffer.Char(msg.ch);
END;
END;
END;
END Handle;
PROCEDURE CommandHistory(next : BOOLEAN);
VAR string : Strings.String;
BEGIN
textBuffer.lock.Acquire;
IF next THEN
string := history.GetNextCommand();
ELSE
string := history.GetPreviousCommand();
END;
IF (string # NIL) THEN
textBuffer.DeleteCurrentLine;
textBuffer.String(string^);
END;
textBuffer.lock.Release;
END CommandHistory;
PROCEDURE Prompt;
BEGIN
textBuffer.SetColor(LightBlue, Black);
textBuffer.String("A2>");
textBuffer.SetColor(White, Black);
END Prompt;
PROCEDURE Execute;
VAR
context : Commands.Context; writer : Streams.Writer; arg : Streams.StringReader;
commandLine : Strings.String;
nbr : ARRAY 8 OF CHAR;
msg, command : ARRAY 128 OF CHAR;
i, length, res : LONGINT;
BEGIN
commandLine := textBuffer.GetCurrentLine();
Strings.TrimWS(commandLine^);
IF (commandLine^ # "") THEN
history.AddCommand(commandLine);
END;
length := Strings.Length(commandLine^);
i := 0;
WHILE (i < length) & ~IsWhitespace(commandLine[i]) & (i < LEN(command) - 1) DO
command[i] := commandLine[i];
INC(i);
END;
command[i] := 0X;
IF (command = "exit") THEN
Close;
ELSIF (command = "clear") THEN
textBuffer.lock.Acquire;
textBuffer.Clear;
textBuffer.lock.Release;
ELSIF (command = "version") THEN
textBuffer.lock.Acquire;
textBuffer.String(Version);
textBuffer.lock.Release;
ELSIF (command = "") THEN
ELSE
IF (i < length) THEN
NEW(arg, length - i);
arg.SetRaw(commandLine^, i, length - i);
ELSE
NEW(arg, 1); arg.Set("");
END;
NEW(writer, textBuffer.Send, 256);
NEW(context, NIL, arg, writer, writer, SELF);
Commands.Activate(command, context, {Commands.Wait}, res, msg);
context.out.Update; context.error.Update;
IF (res # Commands.Ok) THEN
textBuffer.lock.Acquire;
textBuffer.SetColor(Red, Black);
textBuffer.String("Command execution error, res = ");
Strings.IntToStr(res, nbr);
textBuffer.String(nbr);
textBuffer.String(" ("); textBuffer.String(msg); textBuffer.String(")");
textBuffer.Char(LF);
textBuffer.SetColor(White, Black);
textBuffer.lock.Release;
END;
END;
END Execute;
PROCEDURE Quit;
BEGIN
Inputs.keyboard.Unregister(SELF);
END Quit;
END Shell;
VAR
shell : Shell;
PROCEDURE Subtract(position, value, bufferSize : LONGINT) : LONGINT;
VAR result : LONGINT;
BEGIN
ASSERT((0 <= position) & (position < bufferSize));
value := value MOD bufferSize;
IF (position - value >= 0) THEN result := position - value;
ELSE result := bufferSize - 1 - (value - position);
END;
ASSERT((0 <= result) & (result < bufferSize));
RETURN result;
END Subtract;
PROCEDURE Add(position, value, bufferSize : LONGINT) : LONGINT;
VAR result : LONGINT;
BEGIN
ASSERT((0 <= position) & (position < bufferSize));
result := (position + value) MOD bufferSize;
ASSERT((0 <= result) & (result < bufferSize));
RETURN result;
END Add;
PROCEDURE Difference(end, start, bufferSize : LONGINT) : LONGINT;
VAR result : LONGINT;
BEGIN
IF (end >= start) THEN
result := end - start + 1;
ELSE
result := (end + 1) + (bufferSize - start + 1);
END;
RETURN result;
END Difference;
PROCEDURE ClearLine(VAR line : Line; from, to : LONGINT; color : SHORTINT);
VAR i : LONGINT;
BEGIN
ASSERT((0 <= from) & (from < LineWidth));
ASSERT((0 <= to) & (to < LineWidth));
FOR i := from TO to DO
line[i].ch := SPACE;
line[i].color := color;
END;
END ClearLine;
PROCEDURE IsWhitespace(ch : CHAR) : BOOLEAN;
BEGIN
RETURN (ch = SPACE) OR (ch = TAB) OR (ch = CR) OR (ch = LF);
END IsWhitespace;
PROCEDURE Min(a, b : LONGINT) : LONGINT;
BEGIN
IF (a <= b) THEN RETURN a; ELSE RETURN b; END;
END Min;
PROCEDURE Invalidate(textBuffer : TextBuffer);
VAR offset, index, i, nofLines : LONGINT; line : Line; character : Character; ch : CHAR;
BEGIN
ASSERT(textBuffer # NIL);
ASSERT(textBuffer.lock.HasLock());
offset := 0;
nofLines := 1;
index := textBuffer.firstLineShown;
LOOP
line := textBuffer.lines[index MOD LEN(textBuffer.lines)];
FOR i := 0 TO LineWidth-1 DO
character := line[i];
IF (character.ch = TAB) THEN ch := SPACE; ELSE ch := character.ch; END;
SYSTEM.PUT16(TraceBase + offset, ORD(ch) + 100H * character.color);
INC(offset, 2);
END;
IF (index = textBuffer.lastLine) OR (nofLines = TraceHeight) THEN EXIT; END;
INC(index);
INC(nofLines);
END;
WHILE (nofLines < TraceHeight) DO
FOR i := 0 TO LineWidth-1 DO
SYSTEM.PUT16(TraceBase + offset, ORD(SPACE));
INC(offset, 2);
END;
INC(nofLines);
END;
UpdateCursor(textBuffer);
END Invalidate;
PROCEDURE Open*;
BEGIN {EXCLUSIVE}
IF (shell = NIL) THEN
KernelLog.String("BootShell: Starting shell..."); KernelLog.Ln;
NEW(shell);
END;
END Open;
PROCEDURE Close*;
BEGIN {EXCLUSIVE}
IF (shell # NIL) THEN
shell.Quit;
shell := NIL;
END;
END Close;
PROCEDURE Dump*(context : Commands.Context);
BEGIN {EXCLUSIVE}
IF (shell # NIL) THEN
shell.textBuffer.Dump(context.out);
ELSE
context.out.String("BootShell not started."); context.out.Ln;
END;
END Dump;
PROCEDURE UpdateCursor(textBuffer : TextBuffer);
VAR cursorLocation : LONGINT;
BEGIN
ASSERT(textBuffer # NIL);
ASSERT(textBuffer.lock.HasLock());
cursorLocation := Subtract(textBuffer.cursorPosition, textBuffer.firstLineShown * LineWidth, BufferSize);
Machine.Portout8(3D4H, 0EX);
Machine.Portout8(3D5H, CHR(cursorLocation DIV 100H));
Machine.Portout8(3D4H, 0FX);
Machine.Portout8(3D5H, CHR(cursorLocation MOD 100H));
END UpdateCursor;
PROCEDURE Cleanup;
BEGIN
Close;
END Cleanup;
PROCEDURE Init;
VAR value : ARRAY 32 OF CHAR;
BEGIN
Machine.GetConfig("Diagnosis", value);
Strings.TrimWS(value);
IF (value = "1") THEN
Open;
BEGIN {EXCLUSIVE} AWAIT(shell = NIL); END;
END;
END Init;
BEGIN
Modules.InstallTermHandler(Cleanup);
Init;
END BootShell.
SystemTools.DoCommands
Linker.Link \P../Test/ \.Obx ../Test/IDE.Bin 0100000H 1000H Kernel Traps
ATADisks DiskVolumes DiskFS Keyboard BootShell BootConsole ~
VirtualDisks.Install VM0 E:/Private/A2/WinAos/VM/Old-f001.vmdk ~
Partitions.UpdateBootFile VM0#1 ../Test/IDE.Bin ~
VirtualDisks.Uninstall VM0 ~
~~~
SystemTools.DoCommands
FSTools.DeleteFiles BootShell.img ~
VirtualDisks.Create BootShell.img 2048 512 ~
VirtualDisks.Install -c=80 -h=2 -s=18 -b=512 VDISK0 BootShell.img ~
Linker.Link \P../Test/ \.Obx ../Test/CD.Bin 0100000H 1000H Kernel Traps ProcessInfo SystemTools Keyboard BootShell BootConsole ~
Partitions.Format VDISK0#0 AosFS 640 ../Test/CD.Bin ~
Partitions.SetConfig VDISK0#0
TraceMode="4" TracePort="1" TraceBPS="115200"
ExtMemSize="64"
MaxProcs="-1"
Diagnosis="1"
~
VirtualDisks.Uninstall VDISK0 ~
IsoImages.Make A2Diagnosis.iso BootShell.img ~
~~