;***************************************************************************
;*  REBOOT.ASM
;*
;*      Contains routines used to support a local reboot in the Sys
;*      VM.  To do this, we interact with USER and the Reboot VxD.
;*
;*      This functionality is only present in the 386 KERNEL.
;*
;*      The KRebootInit function is called just after USER is loaded.
;*
;*      The Reboot VxD calls the local reboot proc to terminate an app.
;*      The local reboot proc may fail and cause the VxD to trace until
;*      it is able to kill the task.  See comments in the local reboot
;*      proc for more details.
;*
;*      This code must be located in KERNEL due to the internal KERNEL
;*      information it must access.
;*
;*      Created by JonT starting 12 July 1991
;*
;***************************************************************************

	TITLE	REBOOT - Local Reboot Routines

.xlist
include kernel.inc
include tdb.inc
include protect.inc
include newexe.inc
.list

.386p

MAX_TRACE       EQU     5000h

HookInt1 MACRO
        mov     ax,0204h                ;;Tell VxD to hook/unhook Int 1
        mov     cx,cs                   ;;Point to ISR
        mov     dx,codeOFFSET TraceOut
        movzx   edx,dx
        call    [lpReboot]              ;;Call the VxD API
ENDM

UnhookInt1 MACRO
        mov     ax,0204h                ;;Tell VxD to hook/unhook Int 1
        xor     cx,cx                   ;;Pass a zero in CX to unhook
        call    [lpReboot]              ;;Call the VxD API
ENDM

DataBegin

externD lpReboot
externW curTDB
externW hExeHead
externW hUser
externB Kernel_Flags
externB fTaskSwitchCalled
externW wMyOpenFileReent
externB OutBuf
externW pGlobalHeap

EVEN
globalW wLastSeg, 0
globalW wTraceCount, 0
lpOldInt1       DD      0
szUser          DB      'USER'

DataEnd

externFP Int21Handler
externFP GetPrivateProfileInt
IFDEF DBCS
externFP FarMyIsDBCSLeadByte
ENDIF

;** Note that this goes in the fixed code segment
sBegin	CODE
assumes CS,CODE

;  KRebootInit
;       Initializes the KERNEL Local Reboot functionality and talks with
;       the Reboot VxD.

cProc   KRebootInit, <FAR,PUBLIC>, <si,di,ds>
cBegin
        SetKernelDS

        ;** Get the reboot device entry point if it exists
        xor     di,di                   ;Get a NULL pointer
        mov     es,di                   ;  in case there's no reboot device
        mov     bx,0009h            
        mov     ax,1684h                ;Get device entry point
        int     2fh
        mov     ax,es
        or      ax,di
        jz      SHORT RI_NoRebootDev
        mov     WORD PTR lpReboot[0],di
        mov     WORD PTR lpReboot[2],es

        ;** Set the reboot device call back
        mov     ax, 0201h               ;Reboot VxD #201:  Set callback addr
        mov     di,cs
        mov     es,di
        lea     di,KRebootProc
        lea     si, fTaskSwitchCalled   ;DS:SI points to task switch flag
        call    [lpReboot]

RI_NoRebootDev:

cEnd


;  LocalRebootProc
;
;       Called by the Reboot VxD to cause the current Windows app to be
;       terminated.

cProc   KRebootProc, <FAR,PUBLIC>
cBegin  nogen

        ;** Save all the registers so we can restart if necessary
        pusha                           ;16 bytes
        push    ds                      ;2 bytes
        push    es                      ;2 bytes
        SetKernelDS
        mov     bp,sp                   ;Point with BP

        ;** If the KERNEL debugger is installed, DON'T trace!
	test	Kernel_Flags[2],KF2_SYMDEB ;Debugger installed
IF KDEBUG
        jz      SHORT RP_CheckSeg       ;No debugger, try to trace
        Trace_Out <'LocalReboot:  Debugger installed, nuking app without tracing'>
	jmp	SHORT RP_NukeIt         ;Just try to nuke it here!
ELSE
	jnz	SHORT RP_NukeIt         ;Just try to nuke it here!
ENDIF

        ;** Get the code segment we were executing in
RP_CheckSeg:
        mov     ax,[bp + 22]            ;Get CS from stack (IRET frame)

        ;** See if the owner of the code segment was loaded before USER
        cCall   IsBootSeg               ;Returns TRUE if owned by boot mod
        or      ax,ax                   ;Ok to nuke?
        jnz     RP_TraceOut             ;No, must trace out

RP_NukeIt:

        ;** First, we need to get the filename of the .EXE we're about to
        ;**     nuke.  We do this by getting the name out of the module
        ;**     database.  Module databases are not page locked and also
        ;**     have the fully qualified pathname.  So, we copy just the
        ;**     module name into a buffer in our pagelocked data segment
        cld
        push    ds
        pop     es
        mov     di, dataOFFSET OutBuf   ;Point to start of buffer
        mov     ds, curTDB              ;Get the current TDB
        UnSetKernelDS
        mov     ds, ds:[TDB_pModule]    ;Point to the module database
        mov     si, WORD PTR ds:[ne_crc + 2] ;Points to length byte of module EXE
        mov     cl, [si]                ;Get length byte
        xor     ch, ch
        sub     cl, 8                   ;8 bytes of garbage
        add     si, 8
        mov     bx, si                  ;In case we don't find a slash (bad)
RP_SlashLoop:
        lodsb                           ;Get this char
IFDEF DBCS
        call    FarMyIsDBCSLeadByte
        jc      SHORT RP_NoDBCSChar
        lodsb
        dec     cx
        jmp     SHORT RP_NoSlash        ;It is, can't be a slash
RP_NoDBCSChar:
ENDIF
        cmp     al, '\'                 ;Is this a slash?
        je      SHORT RP_Slash          ;Yes
        cmp     al, '/'
        jne     SHORT RP_NoSlash
RP_Slash:
        mov     bx, si                  ;BX points after last slash
RP_NoSlash:
        loop    RP_SlashLoop
        mov     cx, si                  ;Compute count of characters in 8.3 name
        sub     cx, bx
        mov     si, bx                  ;Point to 8.3 name
        rep     movsb                   ;Copy the string into OutBuf
        xor     al, al                  ;Zero byte
        stosb

        ;** Call the VxD to put up the app name
        push    es                      ;ES points to kernel DS
        pop     ds
        ReSetKernelDS
        mov     di, dataOFFSET OutBuf   ;Point to module name with ES:DI
        mov     ax, 203h                ;Display message through VxD
        call    [lpReboot]
        or      ax, ax                  ;If non-zero, we nuke it
        jz      SHORT RP_NoNuke

IF KDEBUG
        krDebugOut DEB_WARN, <'LocalReboot:  Trying to nuke @ES:DI'>
ENDIF

        ;** Clean out some static info
        mov     wMyOpenFileReent, 0     ;Clear reentrant flag for MyOpenFile

        ;** Call USER's signal proc for the task
        mov     es,curTDB               ;Get the current TDB
        cmp     WORD PTR es:[TDB_USignalProc] + 2,0 ;USER signal proc?
        jz      SHORT @F                ;No
        mov     bx,0666h                ;Death knell
        mov     di,-1
        cCall   es:[TDB_USignalProc],<es,bx,di,es:[TDB_Module],es:[TDB_Queue]>
@@:

        ;** Nuke the app.  Does not return
        mov     ax,4c00h
        DOSCALL

        ;** We're somewhere in a boot module.  Try to trace out by telling
        ;**      VxD to do another instruction
RP_TraceOut:
        mov     ax,[bp + 22]            ;Get CS from stack (IRET frame)
IF KDEBUG
        krDebugOut DEB_WARN, <'LocalReboot:  Tracing out of boot module %AX2'>
ENDIF
        mov     wLastSeg,ax             ;Save for next time
        mov     wTraceCount,0           ;Clear the trace count
        HookInt1

        ;** Set the trace flag
        or      WORD PTR [bp + 24],0100h
        jmp     SHORT RP_SetFlag
        
        ;** Force the trap flag clear on the no nuke case
RP_NoNuke:
        and     WORD PTR [bp + 24],NOT 0100h
        
RP_SetFlag:
        pop     es
        pop     ds
        popa
        STIRET                          ;VxD calls as an interrupt
cEnd    nogen


;  TraceOut
;
;       This routine continues to trace until it traces out of a boot module
;       or until the trace count is up.  If it needs to nuke the app,
;       it jumps to RP_NukeIt which depends only on DS being set.  This
;       call returns via an IRET since it is called as an INT 1 handler.

cProc   TraceOut, <FAR,PUBLIC>
cBegin  nogen

        ;** Save all the registers so we can restart if necessary
        pusha                           ;16 bytes
        push    ds                      ;2 bytes
        push    es                      ;2 bytes
        SetKernelDS
        mov     bp,sp                   ;Point with BP

        ;** We keep tracing forever if the heap is locked
        push    es
	mov	es, pGlobalHeap         ;Point to GlobalInfo structure
	cmp     es:[gi_lrulock], 0      ;Is the global heap busy
        pop     es
        jne     SHORT TO_10             ;Force the trace out
        
        ;** See if the CS is the same as last time
        inc     wTraceCount             ;Bump the count
        cmp     wTraceCount,MAX_TRACE   ;Too many instructions?
IF KDEBUG
        jb      SHORT TO_10             ;Count not exceeded
        mov     cx,MAX_TRACE            ;Get trace count
        Trace_Out <'LocalReboot:  Trace count (#CXh instructions) exceeded'>
        jmp     SHORT TO_Reboot
ELSE
        jae     SHORT TO_Reboot         ;Yes, too many, nuke it
ENDIF

TO_10:  mov     ax,[bp + 22]            ;Get the CS from the IRET frame
        cmp     ax,wLastSeg             ;Same as last time?
        je      SHORT TO_StillBad       ;Yes, don't bother looking up
        mov     wLastSeg,ax             ;Save as last executed segment
        mov     bx,cs                   ;Get our CS
        cmp     ax,bx                   ;Our segment?
        je      SHORT TO_StillBad       ;Yes, can't nuke here!
        cCall   IsBootSeg               ;Returns TRUE if owned by boot mod
        or      ax,ax                   ;Ok to nuke?
        jnz     SHORT TO_StillBad       ;No, must continue tracing
IF KDEBUG
        mov     cx,wTraceCount          ;Get trace count
        Trace_Out <'LocalReboot:  Traced out after #CXh instructions'>
ENDIF

        ;** Unhook the interrupt handler and kill the app now
TO_Reboot:
        UnhookInt1
        jmp     RP_NukeIt               ;Try to nuke the app

        ;** Restore the registers and restart
TO_StillBad:
        or      WORD PTR [bp + 24],0100h ;Set the trace flag
        pop     es
        pop     ds
        popa
        iret                            ;VxD calls as an interrupt

cEnd    nogen


;  IsBootSeg
;
;       Tries to find a code segment somewhere in the initial segments
;       loaded before USER.  The CS value is passed in AX.  Returns
;       TRUE iff the CS was found in a boot module.

cProc   IsBootSeg, <NEAR,PUBLIC>
cBegin  nogen
        SetKernelDS
        mov     dx,ax                   ;Put CS in DX for call
        mov     es,hExeHead             ;Get the first module on chain

IBS_Loop:
        cCall   IsModuleOwner           ;See if we can find the owner
        or      ax,ax                   ;Found?
        jnz     SHORT IBS_End           ;Yes, return TRUE
        mov     ax,es                   ;Get the module handle
        cmp     ax,hUser                ;If we just tried USER, we're done
        je      SHORT IBS_NotFound      ;Not found
        mov     es,es:[6]               ;Nope, get next module to try
        jmp     IBS_Loop

IBS_NotFound:
        xor     ax,ax                   ;Return FALSE

IBS_End:
cEnd


;  IsModuleOwner
;
;       Checks in the EXE header to see if the given segment is in this
;       module.
;       DX is the segment, ES points to the module database
;       Returns TRUE/FALSE in AX.  Doesn't trash DX.

cProc   IsModuleOwner, <NEAR,PUBLIC>
cBegin  nogen
        xor     ax,ax                   ;Get a FALSE just in case
        mov     cx,es:[ne_cseg]         ;Get max number of segments
        jcxz    SHORT IMO_End           ;No segments
        mov     di,es:[ne_segtab]       ;Point to the segment table
IMO_SegLoop:
        cmp     dx,es:[di].ns_handle    ;Is this the correct segment entry?
        jz      SHORT IMO_FoundIt       ;Yes, get out
        add     di,SIZE new_seg1        ;Bump to next entry
        loop    IMO_SegLoop             ;Loop back to check next entry
        jmp     SHORT IMO_End           ;Didn't find it

IMO_FoundIt:
        mov     ax,1                    ;Return that we found it here

IMO_End:

cEnd

sEnd

END

