2025-04-27 07:49:33 -04:00

1005 lines
21 KiB
NASM
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

page ,132
title COMMAND2 - resident code for COMMAND.COM part II
name COMMAND2
;/*
; * Microsoft Confidential
; * Copyright (C) Microsoft Corporation 1991
; * All Rights Reserved.
; */
;
; Revision History
; ================
;
; M038 SR 11/5/90 Changed stuff for Novell RPL. These guys cannot
; reserve memory by changing int 12h and then give it
; back to DOS by changing arenas in autoexec.bat.
; This makes command.com reload transient and this
; cannot be done at this stage.
;
;
.xcref
.xlist
include dossym.inc
include pdb.inc
include syscall.inc
include comsw.asm
include comequ.asm
include resmsg.equ
include comseg.asm
.list
.cref
DATARES segment public byte
extrn Append_State:word
extrn Append_Flag:byte
extrn BMemMes:byte
extrn ComBad:byte
extrn ComDrv:byte
extrn ComSpec:byte
extrn EnvirSeg:word
extrn ExtCom:byte
extrn FRetMes:byte
extrn HaltMes:byte
extrn Handle01:word
extrn InitFlag:BYTE
extrn Int_2e_Ret:dword
extrn Io_Save:word
extrn Io_Stderr:byte
extrn Loading:byte
extrn LTpa:word
extrn MemSiz:word
extrn NoHandMes:byte
extrn OldTerm:dword
extrn Parent:word
extrn PermCom:byte
extrn Prompt:byte
extrn PutBackDrv:byte
extrn PutBackMsg:byte
extrn PutBackSubst:byte
extrn Res_Tpa:word
extrn RetCode:word
extrn Save_Pdb:word
extrn SingleCom:word
extrn Sum:word
extrn Trans:dword
extrn TranVarEnd:byte
extrn TranVars:byte
extrn TrnSeg:word
extrn VerVal:word
extrn ResSize:word
extrn OldDS:word
extrn RStack:word
extrn Ctrlc_Trap:near
extrn CritErr_Trap:near
extrn LodCom_Trap:near
DATARES ends
;;ENVARENA segment public para
;;ENVARENA ends
;;ENVIRONMENT segment public para ; default COMMAND environment
;;ENVIRONMENT ends
INIT segment public para
extrn EnvSiz:word
extrn OldEnv:word
extrn ResetEnv:byte
extrn UsedEnv:word
extrn Chuckenv:byte
INIT ends
TRANDATA segment public byte
extrn trandataend:byte
TRANDATA ends
TRANSPACE segment public byte
extrn transpaceend:byte
extrn headcall:dword
TRANSPACE ends
CODERES segment public byte
public BadMemErr
public ChkSum
;; public EndInit
public GetComDsk2
public Int_2e
public LoadCom
public LodCom
public LodCom1
public RestHand
public SavHand
public SetVect
public THeadFix
public TRemCheck
public TJmp
assume cs:CODERES,ds:NOTHING,es:NOTHING,ss:NOTHING
extrn ContC:near
extrn DskErr:near
extrn Alloc_error:near
;* If we cannot allocate enough memory for the transient or there
; was some other allocation error, we display a message and
; then die.
;SR;
; We will have to make sure that at this entry point and at FatalC,
;ds = DATARES. All jumps to these points are made from only within this file
;and so we should be able to do this
assume ds:DATARES
BadMemErr:
mov dx,offset DATARES:BMemMes ; DX = ptr to msg
FatalC:
;; push cs
;; pop ds
;; assume ds:ResGroup
invoke RPrint
; If this is NOT a permanent (top-level) COMMAND, then we exit;
; we can't do anything else!
cmp PermCom,0
je FatalRet
; We are a permanent command. If we are in the process of the
; magic interrupt (Singlecom) then exit too.
cmp SingleCom,0 ; if permcom and singlecom
jne FatalRet ; must take int_2e exit
; Permanent command. We can't do ANYthing except halt.
mov dx,offset DATARES:HaltMes ; DX = ptr to msg
invoke RPrint
sti
Stall:
jmp Stall ; crash the system nicely
FatalRet:
mov dx,offset DATARES:FRetMes ; DX = ptr to msg
invoke RPrint
FatalRet2:
cmp PermCom,0 ; if we get here and permcom,
jne Ret_2e ; must be int_2e
; Bugbug: this is where we'd want to unhook int 2F, *if* we
; were a non-permanent COMMAND that had hooked it! (Just in
; case we decide to do that.)
mov ax,Parent
mov word ptr ds:Pdb_Parent_Pid,ax
mov ax,word ptr OldTerm
mov word ptr ds:Pdb_Exit,ax
mov ax,word ptr OldTerm+2
mov word ptr ds:Pdb_Exit+2,ax
mov ax,(EXIT shl 8) ; return to lower level
int 21h
Ret_2e:
;SR;
; We will ensure that ds = DATARES for all entries to this place
;
;; push cs
;; pop ds
;; assume ds:resgroup,es:nothing,ss:nothing
assume ds:DATARES
mov SingleCom,0 ; turn off singlecom
mov es,Res_Tpa
mov ah,DEALLOC
int 21h ; free up space used by transient
mov bx,Save_Pdb
mov ah,SET_CURRENT_PDB
int 21h ; current process is user
mov ax,RetCode
cmp ExtCom,0
jne GotECode
xor ax,ax ; internals always return 0
GotECode:
mov ExtCom,1 ; force external
;SR; This is actually returning to the caller. However, the old code had
;ds = RESGROUP so I guess we can keep ds = DATARES for us.
;Yes, int 2eh can corrupt all registers so we are ok.
;
jmp Int_2e_Ret ;"iret"
;*** Int_2e, magic command executer
Int_2e:
assume ds:NOTHING,es:NOTHING,ss:NOTHING
;SR;
; We are going to come here from the stub with the old ds and DATARES value
;pushed on the stack in that order. Pick up this stuff off the stack
;
pop ds ;ds = DATARES
assume ds:DATARES
pop ax
; pop ds:OldDS ;Save old value of ds
pop word ptr Int_2e_Ret
pop word ptr [Int_2e_Ret+2] ; store return address
;pop ax ; chuck flags
add sp,2
;; push cs
;; pop es
push ds
pop es ;es = DATARES
; mov ds,OldDS
mov ds,ax
assume ds:nothing ;ds = old value
mov di,80h
mov cx,64
; Bugbug: cld
rep movsw
mov ah,GET_CURRENT_PDB
int 21h ; get user's header
mov es:Save_Pdb,bx
mov ah,SET_CURRENT_PDB
;; mov bx,cs
;SR;
; Set ds = DATARES because BadMemErr expects this
;
push es
pop ds
assume ds:DATARES
mov bx,ds ;es = our PSP now
int 21h ; current process is me
mov SingleCom,81h
mov ExtCom,1 ; make sure this case forced
;SR;
; We can enter LodCom directly after a command shell is terminated or we
;can fall thru from above. When we enter directly from the stub, the stack
;has the old ds value and the data seg value on the stack, so that ds can
;be properly set. To fake this, we push dummy values here.
;
push ds ;old value of ds
push ds ;data seg value, ds = DATARES
LodCom: ; termination handler
pop ds ;ds = DATARES
assume ds:DATARES
add sp,2
; pop OldDS ;store old ds
cmp ExtCom,0
jne @f ; internal cmd - memory allocated
jmp LodCom1
@@:
mov bx,0FFFFh
mov ah,ALLOC
int 21h
call SetSize
add ax,20h
cmp bx,ax
jnc MemOk ; > 512 byte buffer - good enough
BadMemErrJ:
jmp BadMemErr ; not enough memory
;*** SetSize - get transient size in paragraphs
SetSize proc
assume ds:NOTHING,es:NOTHING
mov ax,offset TRANGROUP:TranSpaceEnd + 15
mov cl,4
shr ax,cl
ret
SetSize endp
MemOk:
assume ds:DATARES ;we have set ds = DATARES
mov ah,ALLOC
int 21h
jc BadMemErrJ ; memory arenas probably trashed
mov ExtCom,0 ; flag not to alloc again
mov Res_Tpa,ax ; save current tpa segment
and ax, 0F000h
add ax, 01000h ; round up to next 64k boundary
jc Bad_Tpa ; memory wrap if carry set
; Make sure that new boundary is within allocated range
mov dx,Res_Tpa
add dx,bx ; compute maximum address
cmp dx,ax ; is 64k address out of range?
jbe Bad_Tpa
; Must have 64K of usable space.
sub dx,ax ; compute the usable space
cmp dx,01000h ; is space >= 64k ?
jae LTpaSet
Bad_Tpa:
mov ax,Res_Tpa
LTpaSet:
mov LTpa,ax ; usable tpa is 64k buffer aligned
mov ax,Res_Tpa ; actual tpa is buffer allocated
add bx,ax
mov MemSiz,bx
call SetSize
sub bx,ax
;
;M038; Start of changes
; Changes for Novell RPL. These guys reserve memory for themselves by
;reducing int 12h size and add this memory to the system at autoexec time by
;running a program that changes arenas. This changes the largest block that
;command.com gets and so changes the transient segment. So, command.com does
;a checksum at the wrong address and thinks that the transient is destroyed
;and tries to reload it. At this point, no Comspec is defined and so the
;reload fails, hanging the system. To get around this we just copy the
;transient from the previous address to the new address(if changed) and
;then let command.com do the checksum. So, if the transient area is not
;corrupted, there will not be any reload. In Novell's case, the transient
;is not really corrupted and so this should work.
;
cmp bx,TrnSeg ;Segment still the same?
je LodCom1 ;yes, dont copy
;
;Check if the new segment is above or below the current move. If the new
;segment is above(i.e new block is larger than previous block), then we
;have to move in the reverse direction
;
mov cx,offset TRANGROUP:TranSpaceEnd ;cx = length to move
ja mov_down ;new seg > old seg, reverse move
xor si,si ;normal move
mov di,si
cld
jmp short copy_trans
mov_down:
mov si,cx ;reverse move, start from end
dec si
mov di,si
std
copy_trans:
push ds
push es
mov es,bx ;dest segment
mov ds,TrnSeg ;source segment
assume ds:nothing
rep movsb ;copy transient
cld
pop es
pop ds
assume ds:DATARES
;
;M038; End of changes
;
mov TrnSeg,bx ;new location of transient
LodCom1:
;; mov ax,cs
;; mov ss,ax
;SR; At this point ds = DATARES which is where the stack is located
;
mov ax,ds
mov ss,ax
assume ss:DATARES
mov sp,offset DATARES:RStack
;; mov ds,ax
assume ds:DATARES
call HeadFix ; close files, restore stdin, stdout
xor bp,bp ; flag command ok
mov ax,-1
xchg ax,VerVal
cmp ax,-1
je NoSetVer
mov ah,SET_VERIFY_ON_WRITE ; AL has correct value
int 21h
NoSetVer:
cmp SingleCom,-1
jne NoSng
jmp FatalRet2 ; we have finished the single command
NoSng:
call ChkSum ; check the transient
cmp dx,Sum
je HavCom ; transient ok
Bogus_Com:
mov Loading,1 ; flag DskErr routine
call LoadCom
ChkSame:
call ChkSum
cmp dx,Sum
jz HavCom ; same command
Also_Bogus:
call WrongCom
jmp short ChkSame
HavCom:
mov Loading,0 ; flag to DskErr
mov si,offset DATARES:TranVars
mov di,offset TRANGROUP:HeadCall
mov es,TrnSeg
cld
mov cx,offset DATARES:TranVarEnd
sub cx,si
rep movsb ; transfer info to transient
mov ax,MemSiz
mov word ptr ds:Pdb_Block_Len,ax ; adjust my own header
;*** TJmp - jump-off to transient
;
; Public label so debugger can find this spot.
TJmp:
jmp Trans
;*** TRemCheck - far version of RemCheck for transient
TRemCheck proc far
pop ds ;ds = DATARES
add sp,2 ;discard old value of ds
call RemCheck
ret
TRemCheck endp
;*** RemCheck
;
; ENTRY AL = drive (0=default, 1=A, ...)
;
; EXIT ZR set if removeable media
; ZR clear if fixed media
;
; USED none
RemCheck:
savereg <ax,bx>
mov bx,ax
mov ax,(IOCTL shl 8) + 8
int 21h
jnc rcCont
; If an error occurred, assume the media is non-removable.
; AX contains the non-zero error code from the int 21, so
; 'or ax,ax; sets non-zero. This behavior makes network drives
; appear to be non-removable.
or ax,ax
jmp short ResRegs
rcCont:
and ax,1
not ax
ResRegs:
restorereg <bx,ax>
ret
;*** THeadFix
;
; Far version of HeadFix, called from transient.
THeadFix proc far
pop ds ;ds = DATARES
add sp,2 ;discard old ds value on stack
call HeadFix
ret
THeadFix endp
;*** HeadFix
HeadFix:
call SetVect ; set vectors to our values
; Clean up header
; Bugbug: optimize:
; mov word ptr ds:Pdb_Jfn_Table,cx instead of separate bytes
xor bx,bx ; BX = handle = 0
mov cx,Io_Save ; CX = original stdin, stdout
mov dx,word ptr ds:Pdb_Jfn_Table ; DX = current stdin, stdout
cmp cl,dl
je Chk1 ; stdin matches
mov ah,CLOSE
int 21h ; close stdin
mov ds:Pdb_Jfn_Table,cl ; restore stdin
Chk1:
inc bx ; BX = handle = 1
cmp ch,dh
je ChkStderr ; stdout matches
mov ah,CLOSE
int 21h ; close stdout
mov ds:Pdb_Jfn_Table+1,ch ; restore stdout
ChkStderr:
inc bx ; BX = handle = 2
mov dl,byte ptr ds:[Pdb_Jfn_Table+2] ; Dl = current stderr
mov cl,Io_Stderr ; Cl = original stderr
cmp dl,cl
je ChkOtherHand ; stderr matches
mov ah,CLOSE
int 21h ; close stderr
mov ds:Pdb_Jfn_Table+2,cl ; restore stderr
ChkOtherHand:
add bx,3 ; skip handles 3,4
ifdef NEC_98
add bx,4 ; skip handles 2,3,4
endif ;NEC_98
mov cx,FILPERPROC - 5 ; CX = # handles to close
; (handles 0-4 already done)
;; williamh: March 30, 1993, don't close invalid handle , save some time
push si
mov si, pdb_jfn_table ;go to the handle table
CloseLoop:
cmp byte ptr [bx][si], 0ffh
je Skip_this_handle
mov ah,CLOSE
int 21h ; close each handle
Skip_this_handle:
inc bx
loop CloseLoop
pop si
; Bugbug: since this is for transient code, move it there
; M012: remove this CS -> DS. Must've been missed during
; purification.
;; push ds ; save data segment
;; push cs ; get local segment into DS
;; pop ds ;
cmp Append_Flag,-1 ; do we need to reset APPEND?
jne Append_Fix_End ; no - just exit
mov ax,AppendSetState ; set the state of Append
mov bx,Append_State ; back to the original state
int 2Fh ;
mov Append_Flag,0 ; set append flag to invalid
Append_Fix_End: ;
;; pop ds ; get data segment back
ret
;*** SavHand - save current program's stdin/out & set to our stderr
;
; ENTRY nothing
;
; EXIT nothing
;
; USED flags
;
; EFFECTS
; Handle01 = current program's stdin,stdout JFN entries
; current program's stdin,stdout set to our stderr
;
;SR;
; Changed ds = DATARES. We need it to access our JFN_Table
; Called from ContC ( ds = DATARES ) and DskErr ( ds = DATARES ).
;
SavHand proc
assume ds:DATARES,es:NOTHING,ss:NOTHING
push bx ;preserve registers
push ax
push es
push ds ; save DATARES value
mov ah,GET_CURRENT_PDB
int 21h ; BX = user's header seg addr
mov ds,bx ; DS = user's header seg addr
lds bx,ds:PDB_JFN_POINTER ; DS:BX = ptr to JFN table
mov ax,word ptr ds:[bx] ; AX = stdin,stdout JFN's
pop es ;es = DATARES
push es ;save it back on stack
mov es:Handle01,ax ; save user's stdin, stdout
;SR;
; Use es to address Handle01 & our JFN_Table
;
mov al,es:[PDB_JFN_TABLE+2] ; AL = COMMAND stderr
mov ah,al ; AH = COMMAND stderr
mov word ptr ds:[bx],ax ; set user's stdin/out to our stderr
pop ds ; restore registers
pop es
pop ax
pop bx
ret
SavHand endp
assume ds:DATARES
GetComDsk2:
call GetComDsk
jmp LodCom1 ; memory already allocated
RestHand:
push ds
push bx ; restore stdin, stdout to user
push ax
mov ah,GET_CURRENT_PDB
int 21h ; point to user's header
mov ax,Handle01
mov ds,bx
assume ds:NOTHING
lds bx,ds:Pdb_Jfn_Pointer ; DS:BX = ptr to jfn table
mov word ptr ds:[bx],ax ; stuff his old 0 and 1
pop ax
pop bx
pop ds
ret
assume ds:DATARES,ss:DATARES
Hopeless:
mov dx,offset DATARES:ComBad
jmp FatalC
GetComDsk:
mov al,ComDrv
call RemCheck
jnz Hopeless ; non-removable media
GetComDsk3:
cmp dx,offset DATARES:ComBad
jnz GetComDsk4
mov dx,offset DATARES:ComBad ; DX = ptr to msg
invoke RPrint ; say COMMAND is invalid
GetComDsk4:
; Bugbug: there's always a drive here? No need to check?
cmp PutBackDrv,0 ; is there a drive in the comspec?
jnz Users_Drive ; yes - use it
mov ah,GET_DEFAULT_DRIVE ; use default drive
int 21h
add al,"A" ; convert to ascii
mov PutBackDrv,al ; put in message to print out
Users_Drive:
mov dx,offset DATARES:PutBackMsg ; prompt for diskette
mov si,offset DATARES:PutBackSubst ; containing COMMAND
invoke RPrint
mov dx,offset DATARES:Prompt ; "Press any key"
invoke RPrint
call GetRawFlushedByte
ret
;*** GetRawFlushedByte - flush world and get raw input
GetRawFlushedByte:
mov ax,(STD_CON_INPUT_FLUSH shl 8) or RAW_CON_INPUT
int 21h ; get char without testing or echo
mov ax,(STD_CON_INPUT_FLUSH shl 8) + 0
int 21h
; Bugbug: get rid of this return and the following retz.
return
;*** LoadCom - load in transient
LoadCom:
inc bp ; flag command read
mov dx,offset DATARES:ComSpec
mov ax,OPEN shl 8
int 21h ; open command.com
jnc ReadCom
cmp ax,ERROR_TOO_MANY_OPEN_FILES
jnz TryDoOpen
mov dx,offset DATARES:NoHandMes
jmp FatalC ; will never find a handle
TryDoOpen:
call GetComDsk
jmp LoadCom
ReadCom:
mov bx,ax ; BX = handle
mov dx,offset RESGROUP:TranStart
xor cx,cx ; CX:DX = seek loc
mov ax,LSEEK shl 8
int 21h
jc WrongCom1
mov cx,offset TRANGROUP:TranSpaceEnd - 100h
push ds
mov ds,TrnSeg
assume ds:NOTHING
mov dx,100h
mov ah,READ
int 21h
pop ds
assume ds:DATARES
WrongCom1:
pushf
push ax
mov ah,CLOSE
int 21h ; close command.com
pop ax
popf
jc WrongCom ; error on read
cmp ax,cx
retz ; size matched
WrongCom:
mov dx,offset DATARES:ComBad
call GetComDsk
jmp LoadCom ; try again
;*** ChkSum - compute transient checksum
ChkSum:
push ds
mov ds,TrnSeg
mov si,100h
mov cx,offset TRANGROUP:TranDataEnd - 100H
Check_Sum:
cld
shr cx,1
xor dx,dx
Chk:
lodsw
add dx,ax
adc dx,0
loop Chk
pop ds
ret
;*** SetVect - set interrupt vectors
SetVect:
mov dx,offset DATARES:LodCom_Trap
mov ax,(SET_INTERRUPT_VECTOR shl 8) or 22h
mov word ptr ds:Pdb_Exit,dx
mov word ptr ds:Pdb_Exit+2,ds
int 21h
mov dx,offset DATARES:Ctrlc_Trap
inc al
int 21h
mov dx,offset DATARES:CritErr_Trap
inc al
int 21h
ret
;SR;
; We have this to take care of the extra values pushed on the stack by
;the stub before jumping to LodCom1. We set up ds here and then jump to
;Lodcom1
;
public TrnLodCom1
TrnLodCom1:
pop ds ;ds = DATARES
add sp,2
; pop ds:OldDS
jmp LodCom1
;*** EndInit - end up initialization sequence
;
; Move the environment to a newly allocated segment.
;;EndInit:
;; push ds ; save segments
;; push es ;
;; push cs ; get resident segment to DS
;; pop ds ;
;; assume ds:RESGROUP
;; mov cx,UsedEnv ; get number of bytes to move
;; mov es,EnvirSeg ; get target environment segment
;; assume es:NOTHING
;;
;; mov ds:Pdb_Environ,es ; put new environment in my header
;; mov ds,OldEnv ; source environment segment
;; assume ds:NOTHING
;; xor si,si ; set up offsets to start of segments
;; xor di,di
;; cld
;; rep movsb ; move it
;; xor ax,ax
;; stosb ; make sure it ends with double-null
;;
;; cmp ResetEnv,1 ; do we need to setblock to env end?
;; jne NoReset ; no - we already did it
;; mov bx,EnvSiz ; BX = size of environ in paragraphs
;; push es ; save environment - just to be sure
;; mov ah,SETBLOCK ;
;; int 21h
;; pop es
;;
;;NoReset:
;; mov InitFlag,FALSE ; turn off init flag
;; pop es
;; pop ds
;; jmp LodCom ; allocate transient
;
;The init code has been changed to take care of the new way in which the
;environment segment is allocated.
;NB: We can use all the init variables at this point because they are all in
;RESGROUP
;Bugbug: The above approach will not work for ROMDOS
;
IF 0
EndInit:
push ds
push es ;save segments
push cs
pop ds
assume ds:RESGROUP
;
;Chuckenv flag signals whether it is a passed environment or not
;
mov bx,ds
mov es,bx ;es = RESGROUP
;
;ResSize is the actual size to be retained -- only data for HIMEM COMMAND,
; code + data for low COMMAND
;
mov bx,ResSize ;Total size of resident
mov ah,SETBLOCK
int 21h ;Set block to resident size
;
;Allocate the correct size for the environment
;
mov bx,EnvSiz ;bx = env size in paras
mov ah,ALLOC
int 21h ;get memory
jc nomem_err ;out of memory,signal error
mov EnvirSeg,ax ;Store new environment segment
mov ds:PDB_Environ,ax ;Put new env seg in PSP
mov es,ax ;es = address of allocated memory
assume es:nothing
;
;Copy the environment to the newly allocated segment
;
mov cx,UsedEnv ;number of bytes to move
push ds
mov ds,OldEnv ;ds = Old environment segment
assume ds:nothing
xor si,si
mov di,si ;Start transfer from 0
cld
rep movsb ;Do the copy
xor ax,ax
stosb ;Make it end with double-null
pop ds ;ds = RESGROUP
assume ds:RESGROUP
;
;We have to free the old environment block if it was allocated by INIT
;
cmp Chuckenv,0 ;has env been allocated by INIT?
jne no_free ;no, do not free it
mov ax,OldEnv ;Get old environment
mov es,ax
mov ah,DEALLOC
int 21h ;Free it
no_free:
mov InitFlag,FALSE ;indicate INIT is done
pop es
pop ds
assume ds:nothing
jmp LodCom ;allocate transient
nomem_err:
;
;We call the error routine which will never return. It will either exit
;with an error ( if not the first COMMAND ) or just hang after an error
;message ( if first COMMAND )
;
call Alloc_error
ENDIF
CODERES ends
; This TAIL segment is used to produce a PARA aligned label in
; the resident group which is the location where the transient
; segments will be loaded initial.
TAIL segment public para
org 0
TranStart label word
public TranStart
TAIL ends
; This TAIL segment is used to produce a PARA aligned label in
; the transient group which is the location where the exec
; segments will be loaded initial.
;
; Bugbug: Is TRANTAIL used anymore?
TRANTAIL segment public para
org 0
ExecStart label word
TRANTAIL ends
end