MODULE BootShell; (** AUTHOR "staubesv"; PURPOSE "Simple VGA text mode shell"; *) IMPORT SYSTEM, KernelLog, Machine, Modules, Streams, Commands, Inputs, Strings, Locks; CONST Version = "A2 Bootshell v1.0"; LineWidth = 80; TraceHeight = 25; TraceBase = 0B8000H; (* default screen buffer *) BufferHeight = 2048; (* lines *) BufferSize = BufferHeight * LineWidth; (* characters *) 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 (* Copied from Shell.mod *) 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; (* ring buffer of lines *) lines : ARRAY BufferHeight OF Line; (* index of first line in ring buffer *) firstLine, lastLine : LONGINT; (* index of line currently shown on top of the display *) firstLineShown : LONGINT; (* start and end of currently edited text *) editStartPosition, editEndPosition : LONGINT; (* character position of cursor *) 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: WORD); 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 (* reserve new line in advance *) 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 (* line will be removed *) 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 (* ignore *) 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 (* append *) 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 (* ignore *) 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); (* Select cursor location high register *) Machine.Portout8(3D5H, CHR(cursorLocation DIV 100H)); Machine.Portout8(3D4H, 0FX); (* Select cursor location low register *) 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 ~ ~~