Subversion Repositories SvarDOS

Compare Revisions

Ignore whitespace Rev 348 → Rev 349

/svarcom/command.c
0,0 → 1,326
/*
* SvarCOM is a command-line interpreter.
*
* a little memory area is allocated as high as possible. it contains:
* - a signature (like XMS drivers do)
* - a routine for exec'ing programs
* - a "last command" buffer for input history
*
* when svarcom starts, it tries locating the routine in memory.
*
* if found:
* waits for user input and processes it. if execing something is required, set the "next exec" field in routine's memory and quit.
*
* if not found:
* installs it by creating a new PSP, set int 22 vector to the routine, set my "parent PSP" to the routine
* and quit.
*
*
*
* good lecture about PSP and allocating memory
* https://retrocomputing.stackexchange.com/questions/20001/how-much-of-the-program-segment-prefix-area-can-be-reused-by-programs-with-impun/20006#20006
*
* PSP structure
* http://www.piclist.com/techref/dos/psps.htm
*
*
*
* === MCB ===
*
* each time that DOS allocates memory, it prefixes the allocated memory with
* a 16-bytes structure called a "Memory Control Block" (MCB). This control
* block has the following structure:
*
* Offset Size Description
* 00h byte 'M' = non-last member of the MCB chain
* 'Z' = indicates last entry in MCB chain
* other values cause "Memory Allocation Failure" on exit
* 01h word PSP segment address of the owner (Process Id)
* possible values:
* 0 = free
* 8 = Allocated by DOS before first user pgm loaded
* other = Process Id/PSP segment address of owner
* 03h word number of paragraphs related to this MCB (excluding MCB)
* 05h 11 bytes reserved
* 10h ... start of actual allocated memory block
*/
 
#include <i86.h>
#include <dos.h>
#include <stdio.h>
#include <string.h>
 
#include <process.h>
 
#include "rmod.h"
 
struct config {
int locate;
int install;
} cfg;
 
 
/* returns segment where rmod is installed */
unsigned short install_routine(void) {
char far *ptr, far *mcb;
unsigned short far *owner;
unsigned int memseg = 0xffff;
 
_asm {
/* link in the UMB memory chain for enabling high-memory allocation (and save initial status on stack) */
mov ax, 0x5802 /* GET UMB LINK STATE */
int 0x21
xor ah, ah
push ax /* save link state on stack */
mov ax, 0x5803 /* SET UMB LINK STATE */
mov bx, 1
int 0x21
/* get current allocation strategy and save it in DX */
mov ax, 0x5800
int 0x21
push ax
pop dx
/* set strategy to 'last fit, try high then low memory' */
mov ax, 0x5801
mov bx, 0x0082
int 0x21
/* ask for a memory block and save the given segment to memseg */
mov ah, 0x48
mov bx, (rmod_len + 15) / 16
int 0x21
jc ALLOC_FAIL
mov memseg, ax
ALLOC_FAIL:
/* restore initial allocation strategy */
mov ax, 0x5801
mov bx, dx
int 0x21
/* restore initial UMB memory link state */
mov ax, 0x5803
pop bx /* pop initial UMB link state from stack */
int 0x21
}
 
if (memseg == 0xffff) {
puts("malloc error");
return(0xffff);
}
ptr = MK_FP(memseg, 0);
mcb = MK_FP(memseg - 1, 0);
owner = (void far *)(mcb + 1);
_fmemcpy(ptr, rmod, rmod_len);
 
printf("MCB sig: %c\r\nMCB owner: 0x%04X\r\n", mcb[0], *owner);
{
int i;
for (i = 0; i < 17; i++) {
printf("%02x ", mcb[i]);
}
printf("\r\n");
for (i = 0; i < 17; i++) {
if (mcb[i] > ' ') {
printf(" %c ", mcb[i]);
} else {
printf(" . ", mcb[i]);
}
}
printf("\r\n");
}
 
/* mark memory as "self owned" */
*owner = memseg;
_fmemcpy(mcb + 8, "COMMAND", 8);
return(memseg);
}
 
 
static void parse_argv(struct config *cfg, int argc, char **argv) {
int i;
memset(cfg, 0, sizeof(*cfg));
for (i = 1; i < argc; i++) {
if (strcmp(argv[i], "/locate") == 0) {
cfg->locate = 1;
}
}
}
 
 
/* scan memory for my shared buffer, return segment of buffer */
static unsigned short find_shm(void) {
unsigned short i;
unsigned short far *pattern;
 
/* iterate over all paragraphs, looking for my signature */
for (i = 0; i != 65535; i++) {
pattern = MK_FP(i, 0);
if (pattern[1] != 0x1983) continue;
if (pattern[2] != 0x1985) continue;
if (pattern[3] != 0x2017) continue;
if (pattern[4] != 0x2019) continue;
return(i);
}
return(0xffff);
}
 
 
static int explode_progparams(char *s, char const **argvlist) {
int si = 0, argc = 0;
for (;;) {
/* skip to next non-space character */
while (s[si] == ' ') si++;
/* end of string? */
if (s[si] == '\r') break;
/* set argv ptr */
argvlist[argc++] = s + si;
/* find next space */
while (s[si] != ' ' && s[si] != '\r') si++;
/* is this end of string? */
if (s[si] == '\r') {
s[si] = 0;
break;
}
/* not end: terminate arg and look for next one */
s[si++] = 0;
}
/* terminate argvlist with a NULL value */
argvlist[argc] = NULL;
return(argc);
}
 
 
static void cmd_set(int argc, char const **argv, unsigned short env_seg) {
char far *env = MK_FP(env_seg, 0);
char buff[256];
int i;
while (*env != 0) {
/* copy string to local buff for display */
for (i = 0;; i++) {
buff[i] = *env;
env++;
if (buff[i] == 0) break;
}
puts(buff);
}
}
 
 
int main(int argc, char **argv) {
struct config cfg;
unsigned short env_seg = 0, rmod_seg, rmod_buff = 0;
void far *rmod_func;
 
parse_argv(&cfg, argc, argv);
 
rmod_seg = find_shm();
if (rmod_seg == 0xffff) {
rmod_seg = install_routine();
if (rmod_seg == 0xffff) {
puts("ERROR: install_rmod() failed");
return(1);
} else {
printf("rmod installed at seg 0x%04X\r\n", rmod_seg);
}
} else {
printf("rmod found at seg 0x%04x\r\n", rmod_seg);
}
 
rmod_func = MK_FP(rmod_seg, 0x0A);
/* fetch offset of buffer (result in AX) */
_asm {
call dword ptr [rmod_func]
mov rmod_buff, ax
}
 
printf("rmod_buff at %04X:%04X\r\n", rmod_seg, rmod_buff);
 
_asm {
/* set the int22 handler in my PSP to rmod so DOS jumps to rmod after I terminate */
mov bx, 0x0a
xor ax, ax
mov [bx], ax
mov ax, rmod_seg
mov [bx+2], ax
/* get the segment of my environment */
mov bx, 0x2c
mov ax, [bx]
mov env_seg, ax
}
 
printf("env_seg at %04X\r\n", env_seg);
 
for (;;) {
int i, argcount;
char far *cmdline = MK_FP(rmod_seg, rmod_buff + 2);
char path[256] = "C:\\>$";
char const *argvlist[256];
union REGS r;
 
/* print shell prompt */
r.h.ah = 0x09;
r.x.dx = FP_OFF(path);
intdos(&r, &r);
 
/* wait for user input */
_asm {
push ds
 
/* is DOSKEY support present? (INT 2Fh, AX=4800h, returns non-zero in AL if present) */
mov ax, 0x4800
int 0x2f
mov bl, al /* save doskey status in BL */
 
/* set up buffered input */
mov ax, rmod_seg
push ax
pop ds
mov dx, rmod_buff
 
/* execute either DOS input or DOSKEY */
test bl, bl /* zf set if no DOSKEY present */
jnz DOSKEY
 
mov ah, 0x0a
int 0x21
jmp short DONE
 
DOSKEY:
mov ax, 0x4810
int 0x2f
 
DONE:
pop ds
}
printf("\r\n");
 
/* if nothing entered, loop again */
if (cmdline[-1] == 0) continue;
 
/* copy buffer to a near var (incl. trailing CR) */
_fmemcpy(path, cmdline, cmdline[-1] + 1);
 
argcount = explode_progparams(path, argvlist);
 
/* if nothing args found (eg. all-spaces), loop again */
if (argcount == 0) continue;
 
printf("got %u bytes of cmdline (%d args)\r\n", cmdline[-1], argcount);
for (i = 0; i < argcount; i++) {
printf("arg #%d = '%s'\r\n", i, argvlist[i]);
}
 
/* TODO is it an internal command? */
if (strcmp(argvlist[0], "set") == 0) {
cmd_set(argcount, argvlist, env_seg);
continue;
}
 
execvp(argvlist[0], argvlist);
 
/* execvp() replaces the current process by the new one
if I am still alive then external command failed to execute */
puts("Bad command or file name");
 
}
 
return(0);
}
/svarcom/file2c.c
0,0 → 1,47
/*
* translates a binary file to a C include.
* used by the SvarCOM build process to embedd rcom inside COMMAND.COM
*
* Copyright (C) 2021 Mateusz Viste
*/
 
#include <stdio.h>
 
int main(int argc, char **argv) {
FILE *fdin, *fdout;
unsigned long len;
int c;
 
if (argc != 4) {
puts("usage: file2c infile.dat outfile.c varname");
return(1);
}
 
fdin = fopen(argv[1], "rb");
if (fdin == NULL) {
puts("ERROR: failed to open input file");
return(1);
}
 
fdout = fopen(argv[2], "wb");
if (fdout == NULL) {
fclose(fdin);
puts("ERROR: failed to open output file");
return(1);
}
 
fprintf(fdout, "const char %s[] = {", argv[3]);
for (len = 0;; len++) {
c = getc(fdin);
if (c == EOF) break;
if (len > 0) fprintf(fdout, ",");
if ((len & 15) == 0) fprintf(fdout, "\r\n");
fprintf(fdout, "%3u", c);
}
fprintf(fdout, "};\r\n");
fprintf(fdout, "#define %s_len %lu\r\n", argv[3], len);
 
fclose(fdin);
fclose(fdout);
return(0);
}
/svarcom/makefile
0,0 → 1,37
#
# This is a makefile to build the SVARCOM command interpreter (COMMAND.COM)
#
# You can use following targets:
#
# wmake - compiles the program
# wmake clean - cleans up all non-source files
#
 
CFLAGS = -0 -y -cc -wx -mt -lr -we -d0 -ox -fm=command.map
# -0 generate 8086 compatible code
# -y ignore %WCL% if present
# -cc C code
# -wx maximum warnings level
# -mt TINY memory model
# -lr real-mode target
# -we any warning is considered an error
# -d0 no debug data
# -ox maximum optimization level
 
all: command.com
 
command.com: rmod.h command.c
wcl $(CFLAGS) command.c
 
rmod.h: file2c.com rmod.com
file2c rmod.com rmod.h rmod
 
file2c.com: file2c.c
wcl $(CFLAGS) file2c.c
 
rmod.com: rmod.asm
nasm -f bin -l rmod.lst -o rmod.com rmod.asm
 
clean: .SYMBOLIC
del *.com
del *.obj
/svarcom/rmod.asm
0,0 → 1,97
;
; rmod - resident module of the SvarCOM command interpreter
;
; Copyright (C) 2021 Mateusz Viste
; MIT license
;
; this is installed in memory by the transient part of SvarCOM. it has only
; two jobs: providing a resident buffer for command history, environment, etc
; and respawning COMMAND.COM whenever necessary.
 
CPU 8086
org 0h ; this is meant to be executed without a PSP
 
section .text ; all goes into code segment
 
jmp short skipsig
 
SIG1 dw 0x1983
SIG2 dw 0x1985
SIG3 dw 0x2017
SIG4 dw 0x2019
 
; service routine: used by the transient part of svarcom, returns:
; AX = offset of input buffer history block
; BX = offset where environment's segment is stored (patched at install time)
inputroutine:
mov ax, BUF000
mov bx, ENVSEG
retf
 
skipsig:
 
; set up CS=DS=SS and point SP to my private stack buffer
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, STACKPTR
 
; prepare the exec param block
;mov [EXEC_PARAM_REC], word 0
mov ax, COMSPEC
mov [EXEC_PARAM_REC+2], ax
mov [EXEC_PARAM_REC+4], ds
 
; execute command.com
mov ax, 0x4B00 ; DOS 2+ - load & execute program
mov dx, COMSPEC ; DS:DX - ASCIZ program name TODO: use real COMSPEC...
mov bx, EXEC_PARAM_REC ; ES:BX - parameter block pointer
int 0x21
 
; if all went well, jump back to start
jnc skipsig
 
; update error string so it contains the error number
add al, '0'
mov [ERRLOAD + 4], al
 
; display error message (with trailing COMSPEC)
mov ah, 0x09
mov dx, ERRLOAD
mov [COMSPCZ], byte '$' ; patch comspec terminator to be $
int 0x21
mov [COMSPCZ], byte 0 ; restore initial (NULL) compsec terminator
 
; wait for keypress
mov ah, 0x08
int 0x21
 
; back to program start
jmp skipsig
 
; ExecParamRec used by INT 21h, AX=4b00 (load and execute program), 14 bytes:
; offset size content
; +0 2 segment of environment for child (0 = current)
; +2 4 address of command line to place at PSP:0080
; +6 4 address of an FCB to be placed at PSP:005c
; +0Ah 4 address of an FCB to be placed at PSP:006c
EXEC_PARAM_REC db 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
 
ERRLOAD db "ERR x, FAILED TO LOAD COMMAND.COM FROM:", 13, 10
 
COMSPEC db "C:\SVN\SVARDOS\SVARCOM\COMMAND.COM"
COMSPCZ db 0
 
; input buffer used for the "previous command" history
BUF000 db 128, 0
BUF064 db "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"
BUF128 db "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"
 
; FreeDOS int 21h functions that I use require at least 32 bytes of stack,
; here I allocate 64 bytes to be sure
STACKBUF db "0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF"
STACKPTR db "xx"
 
; environment segment - this is updated by SvarCOM at init time
ENVSEG dw 0
/svarcom/rmod.h
0,0 → 1,25
const char rmod[] = {
235, 15,131, 25,133, 25, 23, 32, 25, 32,184,169, 0,187,109, 1,
203,140,200,142,216,142,192,142,208,188,107, 1,184,134, 0,163,
81, 0,140, 30, 83, 0,184, 0, 75,186,134, 0,187, 79, 0,205,
33,115,222, 4, 48,162, 97, 0,180, 9,186, 93, 0,198, 6,168,
0, 36,205, 33,198, 6,168, 0, 0,180, 8,205, 33,235,194, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 69, 82, 82,
32,120, 44, 32, 70, 65, 73, 76, 69, 68, 32, 84, 79, 32, 76, 79,
65, 68, 32, 67, 79, 77, 77, 65, 78, 68, 46, 67, 79, 77, 32, 70,
82, 79, 77, 58, 13, 10, 67, 58, 92, 83, 86, 78, 92, 83, 86, 65,
82, 68, 79, 83, 92, 83, 86, 65, 82, 67, 79, 77, 92, 67, 79, 77,
77, 65, 78, 68, 46, 67, 79, 77, 0,128, 0, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 48, 49, 50, 51, 52,
53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70,120,120, 0, 0};
#define rmod_len 367
/svarcom/svarcom.txt
0,0 → 1,39
 
=== SVARCOM ===
 
 
SvarCOM is the SvarDOS command line interpreter, known usually under the name
"COMMAND.COM". It is designed and maintained by Mateusz Viste, and distributed
under the terms of the MIT license.
 
So far, it is an incomplete, experimental project. The goal would be to
eventually make SvarCOM the default SvarDOS shell, replacing FreeCOM.
 
Why replacing FreeCOM (FreeDOS COMMAND.COM)?
 
FreeCOM is a very good piece of software, but there are a few things that I
do not like about it. SvarCOM is my attempt at addressing these things:
 
- FreeCOM is not suitable for low-memory machines. It takes about 55K of
conventional memory when XMS is unavailable. XMS being a 386+ thing, FreeCOM
is not a very good fit for pre-386 machines. There is the KSSF hack, but it
is a kludgy hack with many limitations.
 
SvarDOS will not rely on XMS, and will perform swapping that works on any
machine (similar to what MS-DOS did).
 
- FreeCOM requires custom NLS files. While the vast majority of FreeDOS
programs use a single "standard" (CATS/Kitten), FreeCOM is using a different
approach with pre-compiled NLS strings, which make it necessary to
distribute as many binaries as there are supported languages. It also makes
the translation process much more difficult.
 
SvarDOS will use Kitten-style translations, like other applications.
 
- FreeCOM is complex: multi-compiler support and many features. This makes the
code uneasy to follow and changes require careful testing on all supported
compilers.
 
SvarDOS is meant to be simple. It is meant to be compiled with OpenWatcom
and nothing else. It also won't integrate features that can be implemented
as third-party tools (typically: DOSKEY).