/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). |