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 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 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