;MODIFIED 5th October 1995 by Matthew Parker all changes are public domain.

;       SPAWN.ASM - Main function for memory swapping spawn call.
;
;       Public Domain Software written by
;               Thomas Wagner
;               Ferrari electronic GmbH
;               Beusselstrasse 27
;               D-1000 Berlin 21
;               Germany
;
; Modified extensively by Matthew Parker, 3:711/934.31@fidonet, in late
; 1995, in order to make it assemble with Watcom 10.0's assembler.
; All changes are also public domain.  The following problems were
; encountered with WASM:
;
; 1. TYPE does not work on struc's
; 2. The symbol "addr" is confused as a instruction
; 3. Some assumptions about indirect addressing are wrong
;    when using struc's. Notably when making far indirect calls.
; 4. LOCAL statement doesn't like "local a, b, c" must use
;    "local a:word, b:word, c:word" it doesn't assume word length.
; 5. Segment override not allowed to be made an equate
; 6. Full PROC syntax not supported.
; 7. When model set @DataSize is not defined.
; 8. "arg" and "uses" is not supported after the PROC definition.
; 9. Cannot use "SIZE" to declare array size in variable declaration
;
; to assembler with WASM you must define "WATCOM".

;
;
; Assemble with
;
; tasm  /DPASCAL_DEF spawn,spawnp       - Turbo Pascal (Tasm only), near
; tasm  /DPASCAL_DEF /DFARCALL spawn,spawnp - Turbo Pascal (Tasm only), far
; ?asm  spawn;                          - C, default model (small)
; ?asm  /DMODL=large spawn              - C, large model
;
;       NOTE:   For C, change the 'model' directive below according to your
;               memory model, or define MODL=xxx on the command line.
;
;               For Turbo C Huge model, you must give /DTC_HUGE on the
;               command line, or define it here.
;
;
; Main function:
;
;   PASCAL:
;       function do_spawn (swapping: integer;
;                          execfname: string;
;                          cmdtail: string;
;                          envlen: word;
;                          var envp)
;
;   C:
;       int do_spawn (int swapping,
;                     char *execfname,
;                     char *cmdtail,
;                     unsigned envlen,
;                     char *envp)
;
;   Parameters:
;
;       swapping - swap/spawn/exec function:
;                       < 0: Exec, don't swap
;                         0: Spawn, don't swap
;                       > 0: Spawn, swap
;                            in this case, prep_swap must have
;                            been called beforehand (see below).
;
;       cmdtail - command tail for EXEC.
;
;       execfname - name and path of file to execute.
;
;       envlen - length of environment copy (may be 0).
;
;       envp -  pointer to environment block (must be aligned on
;               paragraph boundary). Unused if envlen is 0.
;
;       'cmdtail' and 'execfname' must be zero terminated, even when
;       calling from Pascal. For Pascal, the length byte of the string
;       is ignored.
;
;   Returns:
;       0000..00ff:     Returncode of EXECed program
;       03xx:           DOS-Error xx calling EXEC
;       0500:           Swapping requested, but prep_swap has not
;                       been called or returned an error
;       0501:           MCBs don't match expected setup
;       0502:           Error while swapping out
;
;
; For swapping, the swap method must be prepared before calling do_spawn.
;
;   PASCAL:
;       function prep_swap (method: word; swapfname: string): integer;
;   C:
;       int prep_swap (unsigned method, char *swapfname)
;
;   Parameters:
;
;       method  - bit-map of allowed swap devices:
;                       01 - Allow EMS
;                       02 - Allow XMS
;                       04 - Allow File swap
;                       10 - Try XMS first, then EMS
;                       40 - Create file as "hidden"
;                       80 - Use "create temp" call for file swap
;                      100 - Don't preallocate file
;                      200 - Check for Network, don't preallocate if net
;                     4000 - Environment block will not be swapped
;
;       swapfname - swap file name (may be undefined if the
;                   "method" parameters disallows file swap).
;                   The string must be zero terminated, even
;                   when calling from Pascal. For Pascal, the
;                   length byte of the string is ignored.
;
;   Returns:
;
;       A positive integer on success:
;               1 - EMS swap initialized
;               2 - XMS swap initialized
;               4 - File swap initialized
;       A negative integer on failure:
;               -1 - Couldn't allocate swap space
;               -2 - The spawn module is located too low in memory
;
;
        .model  large,c
;
IFNDEF WATCOM
ptrsize =       @DataSize
ELSE
do_spawn   equ _do_spawn
prep_swap  equ _prep_swap
_psp       equ __psp
ptrsize = 1
ENDIF
;
        extrn   _psp: word
;
        public  do_spawn
        public  prep_swap
;
stacklen        =       256             ; local stack
;
;       "ems_size" is the EMS block size: 16k.
;
ems_size        =       16 * 1024       ; EMS block size
ems_parasize    =       ems_size / 16   ; same in paragraphs
ems_shift       =       10              ; shift factor for paragraphs
ems_paramask    =       ems_parasize-1  ; block mask
;
;       "xms_size" is the unit of measurement for XMS: 1k
;
xms_size        =       1024            ; XMS block size
xms_parasize    =       xms_size / 16   ; same in paragraphs
xms_shift       =       6               ; shift factor for paragraphs
xms_paramask    =       xms_parasize-1  ; block mask
;
;       Method flags
;
USE_EMS         =       01h
USE_XMS         =       02h
USE_FILE        =       04h
XMS_FIRST       =       10h
HIDE_FILE       =       40h
CREAT_TEMP      =       80h
NO_PREALLOC     =       100h
CHECK_NET       =       200h
DONT_SWAP_ENV   =       4000h
;
;       Return codes
;
RC_TOOLOW       =       0102h
RC_BADPREP      =       0500h
RC_MCBERROR     =       0501h
RC_SWAPERROR    =       0502h
;
EMM_INT         =       67h
;
;       The EXEC function parameter block
;
exec_block      struc
envseg  dw      ?               ; environment segment
ppar    dw      ?               ; program parameter string offset
pparseg dw      ?               ; program parameter string segment
fcb1    dw      ?               ; FCB offset
fcb1seg dw      ?               ; FCB segment
fcb2    dw      ?               ; FCB offset
fcb2seg dw      ?               ; FCB segment
exec_block      ends
exec_block_size=0eh
;
;       Structure of an XMS move control block
;
xms_control     struc
lenlo           dw      ?       ; length to move (doubleword)
lenhi           dw      ?
srchnd          dw      ?       ; source handle (0 for standard memory)
srclo           dw      ?       ; source address (doubleword or seg:off)
srchi           dw      ?
desthnd         dw      ?       ; destination handle (0 for standard memory)
destlo          dw      ?       ; destination address (doubleword or seg:off)
desthi          dw      ?
xms_control     ends
xms_control_size=10h
;
;       The structure of the start of an MCB (memory control block)
;
mcb             struc
id              db      ?
owner           dw      ?
paras           dw      ?
mcb             ends
;
;       The structure of an internal MCB descriptor.
;       CAUTION: This structure is assumed to be no larger than 16 bytes
;       in several places in the code, and to be exactly 16 bytes when
;       swapping in from file. Be careful when changing this structure.
;
mcbdesc         struc
__addr            dw      ?   ;For wasm    ; paragraph address of the MCB
msize           dw      ?       ; size in paragraphs (excluding header)
swoffset        dw      ?       ; swap offset (0 in all blocks except first)
swsize          dw      ?       ; swap size (= msize + 1 except in first)
num_follow      dw      ?       ; number of following MCBs
                dw      3 dup(?) ; pad to paragraph (16 bytes)
mcbdesc         ends
mcbdesc_size=10h
;
;       The variable block set up by prep_swap
;
prep_block      struc
xmm             dd      ?               ; XMM entry address
first_mcb       dw      ?               ; Segment of first MCB
psp_mcb         dw      ?               ; Segment of MCB of our PSP
env_mcb         dw      ?               ; MCB of Environment segment
noswap_mcb      dw      ?               ; MCB that may not be swapped
ems_pageframe   dw      ?               ; EMS page frame address
handle          dw      ?               ; EMS/XMS/File handle
total_mcbs      dw      ?               ; Total number of MCBs
swapmethod      db      ?               ; Method for swapping
swapfilename    db      81 dup(?)       ; Swap file name if swapping to file
prep_block      ends
prep_block_size=64h
;
;----------------------------------------------------------------------
;
;       Since we'll be moving code and data around in memory,
;       we can't address locations in the resident block with
;       normal address expressions. MASM does not support
;       defining variables with a fixed offset, so we have to resort
;       to a kludge, and define the shrunk-down code as a structure.
;       It would also be possible to use an absolute segment for the
;       definition, but this is not supported by the Turbo Pascal linker.
;
;       All references to low-core variables from low-core itself
;       are made through DS, so we define a text macro "lmem" that
;       expands to "ds:". When setting up low core from the normal
;       code, ES is used to address low memory, so this can't be used.
;
;lmem    equ     <ds:>
;
;       The memory structure for the shrunk-down code, excluding the
;       code itself. The code follows this block.
;
parseg          struc
                db      2ch dup(?)
psp_envptr      dw      ?
                db      5ch-2eh dup(?)  ; start after PSP
;
save_ss         dw      ?               ; 5C - saved global ss
save_sp         dw      ?               ; 5E - saved global sp
xfcb1           db      16 dup(?)       ; 60..6F - default FCB
xfcb2           db      16 dup(?)       ; 70..7F - default FCB
zero            dw      ?               ; 80 Zero command tail length (dummy)
;
expar           db      SIZE exec_block dup (?) ; exec-parameter-block
spx             dw      ?               ; saved local sp
div0_off        dw      ?               ; divide by zero vector save
div0_seg        dw      ?
filename        db      82 dup(?)       ; exec filename
progpars        db      128 dup(?)      ; command tail
                db      stacklen dup(?) ; local stack space
mystack         db      ?
lprep           db      SIZE prep_block dup(?)  ; the swapping variables
lcurrdesc       db      SIZE mcbdesc dup(?)     ; the current MCB descriptor
lxmsctl         db      SIZE xms_control dup(?)
eretcode        dw      ?               ; EXEC return code
retflags        dw      ?               ; EXEC return flags
cgetmcb         dw      ?               ; address of get_mcb
;
parseg_size=$-parseg
parseg  ends

;
IFNDEF WATCOM
param_len       =       (((SIZE parseg + 1) / 2) * 2)     ; make even
ELSE
param_len       =       (((SIZE parseg + 1) / 2) * 2 + 200h)   ; make even
ENDIF
codebeg         =       param_len
;
        .code
;
;------------------------------------------------------------------------
;
lowcode_begin:
;
;       The following parts of the program code will be moved to
;       low core and executed there, so there must be no absolute
;       memory references.
;       The call to get_mcb must be made indirect, since the offset
;       from the swap-in routine to get_mcb will not be the same
;       after moving.
;
;
;       get_mcb allocates a block of memory by modifying the MCB chain
;       directly.
;
;       On entry, lcurrdesc has the mcb descriptor for the block to
;                 allocate.
;
;       On exit,  Carry is set if the block couldn't be allocated.
;
;       Uses    AX, BX, CX, ES
;       Modifies lprep.first_mcb
;
get_mcb proc    near
;
        mov     ax,ds: lprep.first_mcb
        mov     bx,ds: lcurrdesc.__addr
;
getmcb_loop:
        mov     es,ax
        cmp     ax,bx
        ja      gmcb_abort              ; halt if MCB > wanted
        je      mcb_found               ; jump if same addr as wanted
        add     ax,es:paras             ; last addr
        inc     ax                      ; next mcb
        cmp     ax,bx
        jbe     getmcb_loop             ; Loop if next <= wanted
;
;
;       The wanted MCB starts within the current MCB. We now have to
;       create a new MCB at the wanted position, which is initially
;       free, and shorten the current MCB to reflect the reduced size.
;
        cmp     es:owner,0
        jne     gmcb_abort              ; halt if not free
        mov     bx,es                   ; current
        inc     bx                      ; + 1 (header doesn't count)
        mov     ax,ds: lcurrdesc.__addr
        sub     ax,bx                   ; paragraphs between MCB and wanted
        mov     bx,es:paras             ; paras in current MCB
        sub     bx,ax                   ; remaining paras
        dec     bx                      ; -1 for header
        mov     es:paras,ax             ; set new size for current
        mov     cl,es:id                ; old id
        mov     es:id,4dh               ; set id: there is a next
        mov     ax,ds: lcurrdesc.__addr
        mov     es,ax
        mov     es:id,cl                ; and init to free
        mov     es:owner,0
        mov     es:paras,bx
;
;       We have found an MCB at the right address. If it's not free,
;       abort. Else check the size. If the size is ok, we're done
;       (more or less).
;
mcb_found:
        mov     es,ax
        cmp     es:owner,0
        je      mcb_check               ; continue if free
;
gmcb_abort:
        stc
        ret
;
mcb_check:
        mov     ax,es:paras             ; size
        cmp     ax,ds: lcurrdesc.msize ; needed size
        jae     mcb_ok                  ; ok if enough space
;
;       If there's not enough room in this MCB, check if the next
;       MCB is free, too. If so, coalesce both MCB's and check again.
;
        cmp     es:id,4dh
        jnz     gmcb_abort              ; halt if no next
        push    es                      ; save current
        mov     bx,es
        add     ax,bx
        inc     ax                      ; next MCB
        mov     es,ax
        cmp     es:owner,0              ; next free ?
        jne     gmcb_abort              ; halt if not
        mov     ax,es:paras             ; else load size
        inc     ax                      ; + 1 for header
        mov     cl,es:id                ; and load ID
        pop     es                      ; back to last MCB
        add     es:paras,ax             ; increase size
        mov     es:id,cl                ; and store ID
        jmp     mcb_check               ; now try again
;
;       The MCB is free and large enough. If it's larger than the
;       wanted size, create another MCB after the wanted.
;
mcb_ok:
        mov     bx,es:paras
        sub     bx,ds: lcurrdesc.msize
        jz      mcb_no_next             ; ok, no next to create
        push    es
        dec     bx                      ; size of next block
        mov     ax,es
        add     ax,ds: lcurrdesc.msize
        inc     ax                      ; next MCB addr
        mov     cl,es:id                ; id of this block
        mov     es,ax                   ; address next
        mov     es:id,cl                ; store id
        mov     es:paras,bx             ; store size
        mov     es:owner,0              ; and mark as free
        pop     es                      ; back to old MCB
        mov     es:id,4dh               ; mark next block present
        mov     ax,ds: lcurrdesc.msize ; and set size to wanted
        mov     es:paras,ax
;
mcb_no_next:
        mov     es:owner,cx             ; set owner to current PSP
;
;       Set the 'first_mcb' pointer to the current one, so we don't
;       walk through all the previous blocks the next time.
;       Also, check if the block we just allocated is the environment
;       segment of the program. If so, restore the environment pointer
;       in the PSP.
;
        mov     ax,es
        mov     ds: lprep.first_mcb,ax
        cmp     ds: lprep.env_mcb,ax
        jne     getmcb_finis
        inc     ax
        mov     ds: psp_envptr,ax
;
getmcb_finis:
        clc
        ret                             ; all finished (whew!)
;
get_mcb endp
;
;
ireti:
        iret
;
;
;       The actual EXEC call.
;       Registers on entry:
;               BX      = paragraphs to keep (0 if no swap)
;               CX      = length of environment to copy (words) or zero
;               DS:SI   = environment source
;               ES:DI   = environment destination
;               (ES = our low core code segment)
;
;
;       copy environment buffer down if present
;
doexec:
        jcxz    noenvcpy
        rep movsw
;
noenvcpy:
        push    es                      ; DS = ES = low core = PSP
        pop     ds
        or      bx,bx
        jz      no_shrink
;
;       first, shrink the base memory block down.
;
        mov     ah,04ah
        int     21h                     ; resize memory block
;
;       Again walk all MCBs. This time, all blocks owned by the
;       current process are released.
;
        mov     si,ds: lprep.first_mcb
        or      si,si
        jz      no_shrink
        mov     dx,ds: lprep.psp_mcb
        mov     bx,dx
        inc     bx                      ; base PSP (MCB owner)
        mov     di,ds: lprep.noswap_mcb
;
free_loop:
        cmp     si,dx
        je      free_next               ; don't free base block
        cmp     si,di
        je      free_next
        mov     es,si
        cmp     bx,es:owner             ; our process?
        jne     free_next               ; next if not
        cmp     si,ds: lprep.env_mcb   ; is this the environment block?
        jne     free_noenv
        mov     ds:psp_envptr,0         ; else clear PSP pointer
;
free_noenv:
        inc     si
        mov     es,si
        dec     si
        mov     ah,049h                 ; free memory block
        int     21h
;
free_next:
        mov     es,si
        cmp     es:id,4dh               ; normal block?
        jne     free_ready              ; ready if end of chain
        add     si,es:paras             ; start + length
        inc     si                      ; next MCB
        jmp     free_loop
;
free_ready:
        mov     ax,ds
        mov     es,ax
;
no_shrink:
        mov     dx,filename             ; params for exec
        mov     bx,expar
        mov     ax,04b00h
        int     21h                     ; exec
;
;       Return from EXEC system call. Don't count on any register except
;       CS to be restored (DOS 2.11 and previous versions killed all regs).
;
        mov     bx,cs
        mov     ds,bx
        mov     es,bx
        mov     ss,bx
        mov     sp,ds: spx
        cld
        mov     ds: eretcode,ax        ; save return code
        pushf
        pop     bx
        mov     ds: retflags,bx        ; and returned flags
;
        cmp     ds: lprep.swapmethod,0
        je      exec_memok
        jg      exec_expand
;
;       Terminate.
;
        test    bx,1                    ; carry?
        jnz     exec_term               ; use EXEc retcode if set
        mov     ah,4dh                  ; else get program return code
        int     21h
;
exec_term:
        mov     ah,4ch
        int     21h
;
;
exec_expand:
        mov     ah,4ah                  ; expand memory
        mov     bx,ds: lcurrdesc.msize
        int     21h
        jnc     exec_memok
        mov     ax,4cffh
        int     21h                     ; terminate on error
;
;       Swap memory back
;
        nop
;
exec_memok:
;
;       FALL THROUGH to the appropriate swap-in routine
;
;
getmcboff       =       offset get_mcb - offset lowcode_begin
iretoff         =       offset ireti - offset lowcode_begin
doexec_entry    =       offset doexec - offset lowcode_begin
base_length     =       offset $ - offset lowcode_begin
;
;-----------------------------------------------------------------------
;
;       The various swap in routines follow. Only one of the routines
;       is copied to low memory.
;       Note that the routines are never actually called, the EXEC return
;       code falls through. The final RET thus will return to the restored
;       memory image.
;
;       On entry, DS must point to low core.
;       On exit to the restored code, DS is unchanged.
;
;
;       swapin_ems:     swap in from EMS.
;
swapin_ems      proc    far
;
        xor     bx,bx
        mov     si,ems_parasize
        mov     dx,ds: lprep.handle    ; EMS handle
;
swinems_main:
        push    ds
        mov     cx,ds: lcurrdesc.swsize        ; block length in paras
        mov     di,ds: lcurrdesc.swoffset      ; swap offset
        mov     es,ds: lcurrdesc.__addr          ; segment to swap
        mov     ds,ds: lprep.ems_pageframe     ; page frame address
;
        mov     ax,ems_parasize         ; max length
        sub     ax,si                   ; minus current offset
        jnz     swinems_ok              ; go copy if nonzero
;
swinems_loop:
        mov     ax,4400h                ; map in next page
        int     EMM_INT
        or      ah,ah
        jnz     swinems_error
        mov     si,0                    ; reset offset
        inc     bx                      ; bump up page number
        mov     ax,ems_parasize         ; max length to copy
;
swinems_ok:
        cmp     ax,cx                   ; length to copy
        jbe     swinems_doit            ; go do it if <= total length
        mov     ax,cx                   ; else use total length
;
swinems_doit:
        sub     cx,ax                   ; subtract copy length from total
        push    cx                      ; and save
        push    ax                      ; save the copy length in paras
        push    si
        push    di
        mov     cl,3
        shl     ax,cl                   ; convert to number of words (!)
        inc     cl
        shl     si,cl                   ; convert to byte address
        mov     cx,ax
        rep movsw
        pop     di
        pop     si
        pop     cx                      ; copy length in paras
        mov     ax,es
        add     ax,cx                   ; add copy length to dest segment
        add     si,cx                   ; and EMS page offset
        mov     es,ax
        pop     cx                      ; remaining length
        or      cx,cx                   ; did we copy everything?
        jnz     swinems_loop            ; go loop if not
;
        pop     ds
        cmp     ds: lcurrdesc.num_follow,0     ; another MCB?
        je      swinems_complete        ; exit if not
;
;       Another MCB follows, read next mcb descriptor into currdesc
;
        cmp     si,ems_parasize
        jb      swinems_nonewpage       ; no new block needed
        mov     ax,4400h                ; map page, phys = 0
        int     EMM_INT
        or      ah,ah
        jnz     swinems_error1
        mov     si,0
        inc     bx
;
swinems_nonewpage:
        push    si
        push    ds
        mov     ax,ds
        mov     es,ax
        mov     ds,ds: lprep.ems_pageframe     ; page frame address
        mov     cl,4
        shl     si,cl                   ; convert to byte address
        mov     cx,SIZE mcbdesc
        mov     di,offset lcurrdesc
        rep movsb
        pop     ds
        pop     si
        inc     si                      ; one paragraph
;
        push    bx
        call    ds: cgetmcb
        pop     bx
        jc      swinems_error1
        jmp     swinems_main
;
swinems_complete:
        mov     ah,45h                  ; release EMS pages
        int     EMM_INT
        ret
;
swinems_error:
        pop     ds
swinems_error1:
        mov     ah,45h                  ; release EMS pages on error
        int     EMM_INT
        mov     ax,4cffh
        int     21h                     ; terminate
;
swapin_ems      endp
;
swinems_length  = offset $ - offset swapin_ems
;
;
;       swapin_xms:     swap in from XMS.
;
swapin_xms      proc    far
;
        mov     ax,ds: lprep.handle    ; XMS handle
        mov     ds: lxmsctl.srchnd,ax  ; source is XMS
        mov     ds: lxmsctl.desthnd,0  ; dest is normal memory
        mov     ds: lxmsctl.srclo,0
        mov     ds: lxmsctl.srchi,0
;
swinxms_main:
        mov     ax,ds: lcurrdesc.swsize ; size in paragraphs
        mov     cl,4
        rol     ax,cl                   ; size in bytes + high nibble
        mov     dx,ax
        and     ax,0fff0h               ; low word
        and     dx,0000fh               ; high word
        mov     ds: lxmsctl.lenlo,ax   ; into control block
        mov     ds: lxmsctl.lenhi,dx
        mov     ax,ds: lcurrdesc.swoffset      ; swap offset
        mov     ds: lxmsctl.destlo,ax          ; into control block
        mov     ax,ds: lcurrdesc.__addr          ; segment to swap
        mov     ds: lxmsctl.desthi,ax
        mov     si,offset lxmsctl
        mov     ah,0bh
        call    dword ptr ds:[lprep.xmm] ; move it
        or      ax,ax
        jz      swinxms_error
        mov     ax,ds: lxmsctl.lenlo   ; adjust source addr
        add     ds: lxmsctl.srclo,ax
        mov     ax,ds: lxmsctl.lenhi
        adc     ds: lxmsctl.srchi,ax
;
        cmp     ds: lcurrdesc.num_follow,0     ; another MCB?
        je      swinxms_complete
;
        mov     ds: lxmsctl.lenlo,SIZE mcbdesc
        mov     ds: lxmsctl.lenhi,0
        mov     ds: lxmsctl.desthi,ds
        mov     ds: lxmsctl.destlo,offset lcurrdesc
        mov     si,offset lxmsctl
        mov     ah,0bh
        call    dword ptr ds:[lprep.xmm] ; move it
        or      ax,ax
        jz      swinxms_error
        add     ds: lxmsctl.srclo,16   ; one paragraph
        adc     ds: lxmsctl.srchi,0
;
        call    ds: cgetmcb
        jc      swinxms_error
        jmp     swinxms_main
;
swinxms_complete:
        mov     ah,0ah                  ; release XMS frame
        mov     dx,ds: lprep.handle    ; XMS handle
        call    dword ptr ds:[lprep.xmm]
        ret
;
swinxms_error:
        mov     ah,0ah                  ; release XMS frame on error
        call    dword ptr ds:[lprep.xmm]
        mov     ax,4c00h
        int     21h
;
swapin_xms      endp
;
swinxms_length  = offset $ - offset swapin_xms
;
;
;       swapin_file:    swap in from file.
;
swapin_file     proc    far
;
        IFNDEF  WATCOM
        mov     dx,offset lprep+offset swapfilename; for wasm ;offset lprep.swapfilename
        ELSE
        mov     dx,offset lprep+13h
        ENDIF
        mov     ax,3d00h                        ; open file
        int     21h
        jc      swinfile_error2
        mov     bx,ax                           ; file handle
;
swinfile_main:
        push    ds
        mov     cx,ds: lcurrdesc.swsize        ; size in paragraphs
        mov     dx,ds: lcurrdesc.swoffset      ; swap offset
        mov     ds,ds: lcurrdesc.__addr          ; segment to swap
;
swinfile_loop:
        mov     ax,cx
        cmp     ah,8h                   ; above 32k?
        jbe     swinfile_ok             ; go read if not
        mov     ax,800h                 ; else read 32k
;
swinfile_ok:
        sub     cx,ax                   ; remaining length
        push    cx                      ; save it
        push    ax                      ; and save paras to read
        mov     cl,4
        shl     ax,cl                   ; convert to bytes
        mov     cx,ax
        mov     ah,3fh                  ; read
        int     21h
        jc      swinfile_error
        cmp     ax,cx
        jne     swinfile_error
        pop     cx                      ; paras read
        mov     ax,ds
        add     ax,cx                   ; bump up dest segment
        mov     ds,ax
        pop     cx                      ; remaining length
        or      cx,cx                   ; anything left?
        jnz     swinfile_loop           ; go loop if yes
;
        pop     ds
        cmp     ds: lcurrdesc.num_follow,0     ; another MCB?
        je      swinfile_complete       ; ready if not
        mov     cx,16                   ; read one paragraph
        mov     dx,offset lcurrdesc
        mov     ah,3fh
        int     21h
        jc      swinfile_error1
        cmp     ax,cx
        jne     swinfile_error1
;
        push    bx
        call    ds: cgetmcb
        pop     bx
        jc      swinfile_error1
        jmp     swinfile_main
;
;
swinfile_complete:
        mov     ah,3eh                  ; close file
        int     21h
        IFNDEF  WATCOM
        mov     dx,offset lprep+offset swapfilename; for wasm ;offset lprep.swapfilename
        ELSE
        mov     dx,offset lprep+13h
        ENDIF
        mov     ah,41h                  ; delete file
        int     21h
        ret
;
swinfile_error:
        pop     cx
        pop     cx
        pop     ds
swinfile_error1:
        mov     ah,3eh                  ; close file
        int     21h
swinfile_error2:
        IFNDEF  WATCOM
        mov     dx,offset lprep+offset swapfilename; for wasm ;offset lprep.swapfilename
        ELSE
        mov     dx,offset lprep+13h
        ENDIF
        mov     ah,41h                  ; delete file
        int     21h
        mov     ax,4cffh
        int     21h
;
swapin_file     endp
;
swinfile_length = offset $ - offset swapin_file
;
;
;       swapin_none:    no swap, return immediately.
;
swapin_none     proc    far
;
        ret
;
swapin_none     endp
;
;
        IF      swinems_length GT swinxms_length
swcodelen       =       swinems_length
        ELSE
swcodelen       =       swinxms_length
        ENDIF
        IF      swinfile_length GT swcodelen
swcodelen       =       swinfile_length
        ENDIF
;
swap_codelen    =       ((swcodelen + 1) / 2) * 2
;
codelen         =       base_length + swap_codelen
reslen          =       codebeg + codelen
keep_paras      =       (reslen + 15) shr 4     ; paragraphs to keep
swapbeg         =       keep_paras shl 4        ; start of swap space
savespace       =       swapbeg - 5ch   ; length of overwritten area
;
;--------------------------------------------------------------------
;
        IFDEF   PASCAL_DEF
        .data
        ELSE
        IFDEF   TC_HUGE
        .fardata?       my_data
        ELSE
        .data?
        ENDIF
        ENDIF
;
;
;       Space for saving the part of the memory image below the
;       swap area that is overwritten by our code.
;
save_dat        db      savespace dup(?)
;
;       Variables used while swapping out.
;       The "prep" structure is initialized by prep_swap.
;
prep            prep_block      <>
nextmcb         mcbdesc         <>
currdesc        mcbdesc         <>
xmsctl          xms_control     <>
ems_curpage     dw              ?       ; current EMS page number
ems_curoff      dw              ?       ; current EMS offset (paragraph)
;
;--------------------------------------------------------------------
;
        .code
;
;       swapout_ems:    swap out an MCB block to EMS.
;
;       Entry:  "currdesc"      contains description of block to swap
;               "nextmcb"       contains MCB-descriptor of next block
;                               if currdesc.num_follow is nonzero
;
;       Exit:   0 if OK, != 0 if error, Zero-flag set accordingly.
;
;       Uses:   All regs excpt DS
;
swapout_ems     proc    near
;
        push    ds
        mov     cx,currdesc.swsize      ; block length in paras
        mov     si,currdesc.swoffset    ; swap offset
        mov     dx,prep.handle          ; EMS handle
        mov     bx,ems_curpage          ; current EMS page
        mov     di,ems_curoff           ; current EMS page offset (paras)
        mov     es,prep.ems_pageframe   ; page frame address
        mov     ds,currdesc.__addr        ; segment to swap
;
        mov     ax,ems_parasize         ; max length
        sub     ax,di                   ; minus current offset
        jnz     swems_ok                ; go copy if there's room
;
swems_loop:
        mov     ax,4400h                ; map in next page
        int     EMM_INT
        or      ah,ah
        jnz     swems_error
        mov     di,0                    ; reset offset
        inc     bx                      ; bump up page number
        mov     ax,ems_parasize         ; max length to copy
;
swems_ok:
        cmp     ax,cx                   ; length to copy
        jbe     swems_doit              ; go do it if <= total length
        mov     ax,cx                   ; else use total length
;
swems_doit:
        sub     cx,ax                   ; subtract copy length from total
        push    cx                      ; and save
        push    ax                      ; save the copy length in paras
        push    si
        push    di
        mov     cl,3
        shl     ax,cl                   ; convert to number of words (!)
        inc     cl
        shl     di,cl                   ; convert to byte address
        mov     cx,ax
        rep movsw
        pop     di
        pop     si
        pop     cx                      ; copy length in paras
        mov     ax,ds
        add     ax,cx                   ; add copy length to source segment
        add     di,cx                   ; and EMS page offset
        mov     ds,ax
        pop     cx                      ; remaining length
        or      cx,cx                   ; did we copy everything?
        jnz     swems_loop              ; go loop if not
;
        pop     ds
        cmp     currdesc.num_follow,0   ; another MCB?
        je      swems_complete          ; exit if not
;
;       Another MCB follows, append nextmcb to save block.
;
        cmp     di,ems_parasize
        jb      swems_nonewpage         ; no new block needed
        mov     ax,4400h                ; map page, phys = 0
        int     EMM_INT
        or      ah,ah
        jnz     swems_error1
        mov     di,0
        inc     bx
;
swems_nonewpage:
        push    di
        mov     cl,4
        shl     di,cl                   ; convert to byte address
        mov     cx,SIZE mcbdesc
        mov     si,offset nextmcb
        rep movsb
        pop     di
        inc     di                      ; one paragraph
;
swems_complete:
        mov     ems_curpage,bx
        mov     ems_curoff,di
        xor     ax,ax
        ret
;
swems_error:
        pop     ds
swems_error1:
        mov     ah,45h                  ; release EMS pages on error
        int     EMM_INT
        mov     ax,RC_SWAPERROR
        or      ax,ax
        ret
;
swapout_ems     endp
;
;
;       swapout_xms:    swap out an MCB block to XMS.
;
;       Entry:  "currdesc"      contains description of block to swap
;               "nextmcb"       contains MCB-descriptor of next block
;                               if currdesc.num_follow is nonzero
;
;       Exit:   0 if OK, -1 if error, Zero-flag set accordingly.
;
;       Uses:   All regs excpt DS
;
swapout_xms     proc    near
;
        mov     ax,currdesc.swsize      ; size in paragraphs
        mov     cl,4
        rol     ax,cl                   ; size in bytes + high nibble
        mov     dx,ax
        and     ax,0fff0h               ; low word
        and     dx,0000fh               ; high word
        mov     xmsctl.lenlo,ax         ; into control block
        mov     xmsctl.lenhi,dx
        mov     xmsctl.srchnd,0         ; source is normal memory
        mov     ax,currdesc.swoffset    ; swap offset
        mov     xmsctl.srclo,ax         ; into control block
        mov     ax,currdesc.__addr        ; segment to swap
        mov     xmsctl.srchi,ax
        mov     ax,prep.handle          ; XMS handle
        mov     xmsctl.desthnd,ax
        mov     si,offset xmsctl
        mov     ah,0bh
        call    dword ptr ds:[prep.xmm] ; move it
        or      ax,ax
        jz      swxms_error
        mov     ax,xmsctl.lenlo         ; adjust destination addr
        add     xmsctl.destlo,ax
        mov     ax,xmsctl.lenhi
        adc     xmsctl.desthi,ax
;
        cmp     currdesc.num_follow,0   ; another MCB?
        je      swxms_complete
;
        mov     xmsctl.lenlo,SIZE mcbdesc
        mov     xmsctl.lenhi,0
        mov     xmsctl.srchi,ds
        mov     xmsctl.srclo,offset nextmcb
        mov     si,offset xmsctl
        mov     ah,0bh
        call    dword ptr ds:[prep.xmm]                ; move it
        or      ax,ax
        jz      swxms_error
        add     xmsctl.destlo,16        ; one paragraph
        adc     xmsctl.desthi,0
;
swxms_complete:
        xor     ax,ax
        ret
;
swxms_error:
        mov     ah,0ah                  ; release XMS frame on error
        mov     dx,prep.handle          ; XMS handle
        call    dword ptr ds:[prep.xmm]
        mov     ax,RC_SWAPERROR
        or      ax,ax
        ret
;
swapout_xms     endp
;
;
;       swapout_file:   swap out an MCB block to file.
;
;       Entry:  "currdesc"      contains description of block to swap
;               "nextmcb"       contains MCB-descriptor of next block
;                               if currdesc.num_follow is nonzero
;
;       Exit:   0 if OK, -1 if error, Zero-flag set accordingly.
;
;       Uses:   All regs excpt DS
;
swapout_file    proc    near
;
        push    ds
        mov     cx,currdesc.swsize      ; size in paragraphs
        mov     bx,prep.handle          ; file handle
        mov     dx,currdesc.swoffset    ; swap offset
        mov     ds,currdesc.__addr        ; segment to swap
;
swfile_loop:
        mov     ax,cx
        cmp     ah,8h                   ; above 32k?
        jbe     swfile_ok               ; go write if not
        mov     ax,800h                 ; else write 32k
;
swfile_ok:
        sub     cx,ax                   ; remaining length
        push    cx                      ; save it
        push    ax                      ; and save paras to write
        mov     cl,4
        shl     ax,cl                   ; convert to bytes
        mov     cx,ax
        mov     ah,40h                  ; write
        int     21h
        jc      swfile_error
        cmp     ax,cx
        jne     swfile_error
        pop     cx                      ; paras written
        mov     ax,ds
        add     ax,cx                   ; bump up source segment
        mov     ds,ax
        pop     cx                      ; remaining length
        or      cx,cx                   ; anything left?
        jnz     swfile_loop             ; go loop if yes
;
        pop     ds
        cmp     currdesc.num_follow,0   ; another MCB?
        je      swfile_complete         ; ready if not
        mov     cx,16                   ; write one paragraph
        mov     dx,offset nextmcb
        mov     ah,40h
        int     21h
        jc      swfile_error1
        cmp     ax,cx
        jne     swfile_error1
;
swfile_complete:
        xor     ax,ax
        ret
;
swfile_error:
        pop     cx
        pop     cx
        pop     ds
swfile_error1:
        mov     ah,3eh                  ; close file
        int     21h
        mov     dx,offset prep.swapfilename
        mov     ah,41h                  ; delete file
        int     21h
        mov     ax,RC_SWAPERROR
        or      ax,ax
        ret
;
swapout_file    endp
;
;--------------------------------------------------------------------------
;--------------------------------------------------------------------------
;
;
        IFDEF   PASCAL_DEF
        IFDEF   FARCALL
do_spawn        PROC    far swapping: word, execfname: dword, params: dword, envlen: word, envp: dword
        ELSE
do_spawn        PROC    near swapping: word, execfname: dword, params: dword, envlen: word, envp: dword
        ENDIF
        ELSE
        IFDEF   WATCOM
do_spawn        PROC    uses si di, swapping: word, execfname: dword, params: dword, envlen: word, envp: dword
        ELSE
do_spawn        PROC    uses si di, swapping: word, execfname: ptr, params: ptr, envlen: word, envp: ptr
        ENDIF
        ENDIF

        local   datseg:word, pspseg:word, currmcb:word
;
        IFDEF   TC_HUGE
        mov     ax,SEG my_data
        mov     ds,ax
        ENDIF
;
        mov     datseg,ds               ; save default DS
;
        IFDEF   PASCAL_DEF
        cld
        mov     bx,prefixseg
        ELSE
        IFDEF   TC_HUGE
        mov     ax,SEG _psp
        mov     es,ax
        mov     bx,es:_psp
        ELSE
        mov     bx,_psp
        ENDIF
        ENDIF
        mov     pspseg,bx
;
;
;       Check if spawn is too low in memory
;
        mov     ax,cs
        mov     dx,offset lowcode_begin
        mov     cl,4
        shr     dx,cl
        add     ax,dx                   ; normalized start of this code
        mov     dx,keep_paras           ; the end of the modified area
        add     dx,bx                   ; plus PSP = end paragraph
        cmp     ax,dx
        ja      doswap_ok       ; ok if start of code > end of low mem
        mov     ax,RC_TOOLOW
        ret
;
doswap_ok:
        cmp     word ptr swapping,0
        jle     method_ok
;
;       check the swap method, to make sure prep_swap has been called
;
        mov     al,prep.swapmethod
        cmp     al,USE_EMS
        je      method_ok
        cmp     al,USE_XMS
        je      method_ok
        cmp     al,USE_FILE
        je      method_ok
        mov     ax,RC_BADPREP
        ret
;
;       Save the memory below the swap space.
;       We must do this before swapping, so the saved memory is
;       in the swapped out image.
;       Anything else we'd want to save on the stack or anywhere
;       else in "normal" memory also has to be saved here, any
;       modifications done to memory after the swap will be lost.
;
;       Note that the memory save is done even when not swapping,
;       because we use some of the variables in low core for
;       simplicity.
;
method_ok:
        push    ds
        pop     es
        push    ds
        mov     ds,pspseg               ; DS points to PSP
        mov     si,5ch
        mov     di,offset save_dat
        mov     cx,savespace / 2        ; NOTE: savespace is always even
        rep movsw
        pop     ds
;
        mov     ax,word ptr swapping
        cmp     ax,0
        jg      begin_swap
;
;       not swapping, prep_swap wasn't called. Init those variables in
;       the 'prep' block we need in any case.
;
        mov     prep.swapmethod,al
        je      no_reduce
;
        mov     ax,pspseg
        dec     ax
        mov     prep.psp_mcb,ax
        mov     prep.first_mcb,ax
        inc     ax
        mov     es,ax
        mov     bx,es:psp_envptr
        mov     prep.env_mcb,bx
        mov     prep.noswap_mcb,0
        cmp     word ptr envlen,0
        jne     swp_can_swap_env
        mov     prep.noswap_mcb,bx
;
swp_can_swap_env:
        xor     bx,bx
        mov     es,bx
        mov     ah,52h                  ; get list of lists
        int     21h
        mov     ax,es
        or      ax,bx
        jz      no_reduce
        mov     es,es:[bx-2]            ; first MCB
        cmp     es:id,4dh               ; normal ID?
        jne     no_reduce
        mov     prep.first_mcb,es
;
no_reduce:
        jmp     no_swap1
;
;       set up first block descriptor
;
begin_swap:
        mov     ax,prep.first_mcb
        mov     currmcb,ax
        mov     es,prep.psp_mcb         ; let ES point to base MCB
        mov     ax,es:paras
        mov     currdesc.msize,ax
        sub     ax,keep_paras
        mov     currdesc.swsize,ax
        mov     currdesc.__addr,es
        mov     currdesc.swoffset,swapbeg + 16
;               NOTE: swapbeg is 1 para higher when seen from MCB
        mov     ax,prep.total_mcbs
        mov     currdesc.num_follow,ax
;
;       init other vars
;
        mov     xmsctl.destlo,0
        mov     xmsctl.desthi,0
        mov     ems_curpage,0
        mov     ems_curoff,ems_parasize
;
;       Do the swapping. Each MCB block (except the last) has an
;       "mcbdesc" structure appended that gives location and size
;       of the next MCB.
;
swapout_main:
        cmp     currdesc.num_follow,0   ; next block?
        je      swapout_no_next         ; ok if not
;
;       There is another MCB block to be saved. So we don't have
;       to do two calls to the save routine with complicated
;       parameters, we set up the next MCB descriptor beforehand.
;       Walk the MCB chain starting at the current MCB to find
;       the next one belonging to this process.
;
        mov     ax,currmcb
        mov     bx,pspseg
        mov     cx,prep.psp_mcb
        mov     dx,prep.noswap_mcb
;
swm_mcb_walk:
        mov     es,ax
        cmp     ax,cx
        je      swm_next_mcb
        cmp     ax,dx
        je      swm_next_mcb
;
        cmp     bx,es:owner             ; our process?
        je      swm_mcb_found           ; found it if yes
;
swm_next_mcb:
        cmp     es:id,4dh               ; normal block?
        jne     swm_mcb_error           ; error if end of chain
        add     ax,es:paras             ; start + length
        inc     ax                      ; next MCB
        jmp     swm_mcb_walk
;
;       MCB found, set up an mcbdesc in the "nextmcb" structure
;
swm_mcb_found:
        mov     nextmcb.__addr,es
        mov     ax,es:paras             ; get number of paragraphs
        mov     nextmcb.msize,ax        ; and save
        inc     ax
        mov     nextmcb.swsize,ax
        mov     bx,es
        add     bx,ax
        mov     currmcb,bx
        mov     nextmcb.swoffset,0
        mov     ax,currdesc.num_follow
        dec     ax
        mov     nextmcb.num_follow,ax
;
swapout_no_next:
        cmp     prep.swapmethod,USE_EMS
        je      swm_ems
        cmp     prep.swapmethod,USE_XMS
        je      swm_xms
        call    swapout_file
        jmp     short swm_next
;
swm_ems:
        call    swapout_ems
        jmp     short swm_next
;
swm_xms:
        call    swapout_xms
;
swm_next:
        jnz     swapout_error
        cmp     currdesc.num_follow,0
        je      swapout_complete
;
;       next MCB exists, copy the "nextmcb" descriptor into
;       currdesc, and loop.
;
        mov     es,datseg
        mov     si,offset nextmcb
        mov     di,offset currdesc
        mov     cx,SIZE mcbdesc
        rep movsb
        jmp     swapout_main
;
;
swm_mcb_error:
        cmp     prep.swapmethod,USE_FILE
        je      swm_mcberr_file
        cmp     prep.swapmethod,USE_EMS
        je      swm_mcberr_ems
;
        mov     ah,0ah                  ; release XMS frame on error
        mov     dx,prep.handle          ; XMS handle
        call    dword ptr ds:[prep.xmm]
        mov     ax,RC_MCBERROR
        jmp     short swapout_error
;
swm_mcberr_ems:
        mov     dx,prep.handle          ; EMS handle
        mov     ah,45h                  ; release EMS pages on error
        int     EMM_INT
        mov     ax,RC_MCBERROR
        jmp     short swapout_error
;
swm_mcberr_file:
        mov     ah,3eh                  ; close file
        mov     bx,prep.handle
        int     21h
        mov     dx,offset prep.swapfilename
        mov     ah,41h                  ; delete file
        int     21h
        mov     ax,RC_MCBERROR
;
swapout_error:
        ret
;
;
;       Swapout complete. Close the handle (EMS/file only),
;       then set up low memory.
;
swapout_complete:
        cmp     prep.swapmethod,USE_FILE
        jne     swoc_nofile
;
;       File swap: Close the swap file to make the handle available
;
        mov     bx,prep.handle
        mov     ah,3eh
        int     21h                     ; close file
        mov     si,offset swapin_file
        jnc     swoc_ready
        mov     ax,RC_SWAPERROR
        jmp     swapout_error
;
swoc_nofile:
        cmp     prep.swapmethod,USE_EMS
        jne     swoc_xms
;
;       EMS: Unmap page
;
        mov     ax,4400h
        mov     bx,-1
        mov     dx,prep.handle
        int     EMM_INT
        mov     si,offset swapin_ems
        jmp     short swoc_ready
;
swoc_xms:
        mov     si,offset swapin_xms
        jmp     short swoc_ready
;
no_swap1:
        mov     si,offset swapin_none
;
;       Copy the appropriate swap-in routine to low memory.
;
swoc_ready:
        mov     es,pspseg
        mov     cx,swap_codelen / 2
        mov     di,codebeg + base_length
        push    ds
        mov     ax,cs
        mov     ds,ax
        rep movsw
;
;       And while we're at it, copy the MCB allocation routine (which
;       also includes the initial MCB release and exec call) down.
;
        mov     cx,base_length / 2
        mov     di,param_len
        mov     si,offset lowcode_begin
        rep movsw
;
        pop     ds
        mov     bx,es
        dec     bx
        mov     es,bx           ; let ES point to base MCB
;
;       Again set up the base MCB descriptor, and copy it as well as
;       the variables set up by prep_swap to low memory.
;       This isn't too useful if we're not swapping, but it doesn't
;       hurt, either. The only variable used when not swapping is
;       lprep.swapmethod.
;
        mov     ax,es:paras
        mov     currdesc.msize,ax
        sub     ax,keep_paras
        mov     currdesc.swsize,ax
        mov     currdesc.__addr,es
        mov     currdesc.swoffset,swapbeg + 16
        mov     ax,prep.total_mcbs
        mov     currdesc.num_follow,ax
;
        mov     es,pspseg               ; ES points to PSP again
;
        mov     cx,SIZE prep_block
        mov     si,offset prep
        mov     di,offset lprep
        rep movsb
        mov     cx,SIZE mcbdesc
        mov     si,offset currdesc
        mov     di,offset lcurrdesc
        rep movsb
;
;       now set up other variables in low core
;
        mov     es:cgetmcb,getmcboff + codebeg
        mov     es:eretcode,0
        mov     es:retflags,0
;
;       Prepare exec parameter block
;
        mov     ax,es
        mov     es:expar.fcb1seg,ax
        mov     es:expar.fcb2seg,ax
        mov     es:expar.pparseg,ax
        mov     es:expar.envseg,0
;
;       The 'zero' word is located at 80h in the PSP, the start of
;       the command line. So as not to confuse MCB walking programs,
;       a command line length of zero is inserted here.
;
        mov     es:zero,0d00h           ; 00h,0dh = empty command line
;
;       Init default fcb's by parsing parameter string
;
        IF      ptrsize
        lds     si,dword ptr params
        ELSE
        mov     si,params
        ENDIF
        IFDEF   PASCAL_DEF
        inc     si                      ; skip length byte
        ENDIF
        push    si
        mov     di,xfcb1
        mov     es:expar.fcb1,di
        push    di
        mov     cx,16
        xor     ax,ax
        rep stosw                       ; init both fcb's to 0
        pop     di
        mov     ax,2901h
        int     21h
        mov     di,xfcb2
        mov     es:expar.fcb2,di
        mov     ax,2901h
        int     21h
        pop     si
;
;       move command tail string into low core
;
        mov     di,progpars
        mov     es:expar.ppar,di
        xor     cx,cx
        inc     di
cmdcpy:
        lodsb
        or      al,al
        jz      cmdcpy_end
        stosb
        inc     cx
        jmp     cmdcpy
;
cmdcpy_end:
        mov     al,0dh
        stosb
        mov     es:progpars,cl
;
;       move filename string into low core
;
        IF      ptrsize
        lds     si,dword ptr execfname
        ELSE
        mov     si,execfname
        ENDIF
        IFDEF   PASCAL_DEF
        inc     si
        ENDIF
        mov     di,filename
fncpy:
        lodsb
        stosb
        or      al,al
        jnz     fncpy
;
;       Setup environment copy
;
        mov     bx,keep_paras           ; paras to keep
        mov     cx,word ptr envlen               ; environment size
        jcxz    no_environ              ; go jump if no environment
        cmp     word ptr swapping,0
        jne     do_envcopy
;
;       Not swapping, use the environment pointer directly.
;       Note that the environment copy must be paragraph aligned.
;
        IF      ptrsize
        mov     ax,word ptr envp+2
        mov     bx,word ptr envp
        ELSE
        mov     ax,ds
        mov     bx,envp
        ENDIF
        add     bx,15                   ; make sure it's paragraph aligned
        mov     cl,4
        shr     bx,cl                   ; and convert to segment addr
        add     ax,bx
        mov     es:expar.envseg,ax      ; new environment segment
        xor     cx,cx                   ; mark no copy
        xor     bx,bx                   ; and no shrink
        jmp     short no_environ
;
;       Swapping or EXECing without return. Set up the pointers for
;       an environment copy (we can't do the copy yet, it might overwrite
;       this code).
;
do_envcopy:
        inc     cx
        shr     cx,1                    ; words to copy
        mov     ax,cx                   ; convert envsize to paras
        add     ax,7
        shr     ax,1
        shr     ax,1
        shr     ax,1
        add     bx,ax                   ; add envsize to paras to keep
        IF      ptrsize
        lds     si,dword ptr envp
        ELSE
        mov     si,envp
        ENDIF
;
        mov     ax,es                   ; low core segment
        add     ax,keep_paras           ; plus fixed paras
        mov     es:expar.envseg,ax      ; = new environment segment
;
;       Save stack regs, switch to local stack
;
no_environ:
        mov     es:save_ss,ss
        mov     es:save_sp,sp
        mov     ax,es
        mov     ss,ax
        mov     sp,offset mystack
;
        push    cx                      ; save env length
        push    si                      ; save env pointer
        push    ds                      ; save env segment
;
;       save and patch INT0 (division by zero) vector
;
        xor     ax,ax
        mov     ds,ax
        mov     ax,word ptr ds:0
        mov     es:div0_off,ax
        mov     ax,word ptr ds:2
        mov     es:div0_seg,ax
        mov     word ptr ds:0,codebeg + iretoff
        mov     word ptr ds:2,es
;
        pop     ds                      ; pop environment segment
        pop     si                      ; pop environment offset
        pop     cx                      ; pop environment length
        mov     di,swapbeg              ; environment destination
;
;       Push return address on local stack
;
        push    cs                      ; push return segment
        mov     ax,offset exec_cont
        push    ax                      ; push return offset
        mov     es:spx,sp               ; save stack pointer
;
;       Goto low core code
;
        push    es                      ; push entry segment
        mov     ax,codebeg + doexec_entry
        push    ax                      ; push entry offset
;       ret     far                     ; can't use RET here because
        db      0cbh                    ; of .model
;
;----------------------------------------------------------------
;
;       Low core code will return to this location, with DS set to
;       the PSP segment.
;
exec_cont:
        push    ds
        pop     es
        mov     ss,ds:save_ss           ; reload stack
        mov     sp,ds:save_sp
;
;       restore INT0 (division by zero) vector
;
        xor     cx,cx
        mov     ds,cx
        mov     cx,es:div0_off
        mov     word ptr ds:0,cx
        mov     cx,es:div0_seg
        mov     word ptr ds:2,cx
;
        mov     ax,es:eretcode
        mov     bx,es:retflags
        mov     ds,datseg
;
;       Restore overwritten part of program
;
        mov     si,offset save_dat
        mov     di,5ch
        mov     cx,savespace
        rep movsb
;
        test    bx,1                    ; carry set?
        jnz     exec_fault              ; return EXEC error code if fault
        mov     ah,4dh                  ; else get program return code
        int     21h
        ret
;
exec_fault:
        mov     ah,3                    ; return error as 03xx
        ret
;
do_spawn        ENDP
;
;----------------------------------------------------------------------------
;----------------------------------------------------------------------------
;
emm_name        db      'EMMXXXX0'
;
;       prep_swap - prepare for swapping.
;
;       This routine checks all parameters necessary for swapping,
;       and attempts to set up the swap-out area in EMS/XMS, or on file.
;       In detail:
;
;            1) Check whether the do_spawn routine is located
;               too low in memory, so it would get overwritten.
;               If this is true, return an error code (-2).
;
;            2) Walk the memory control block chain, adding up the
;               paragraphs in all blocks assigned to this process.
;
;            3) Check EMS (if the method parameter allows EMS):
;               - is an EMS driver installed?
;               - are sufficient EMS pages available?
;               if all goes well, the EMS pages are allocated, and the
;               routine returns success (1).
;
;            4) Check XMS (if the method parameter allows XMS):
;               - is an XMS driver installed?
;               - is a sufficient XMS block available?
;               if all goes well, the XMS block is allocated, and the
;               routine returns success (2).
;
;            5) Check file swap (if the method parameter allows it):
;               - try to create the file
;               - pre-allocate the file space needed by seeking to the end
;                 and writing a byte.
;               If the file can be written, the routine returns success (4).
;
;            6) Return an error code (-1).
;
        IFDEF   PASCAL_DEF
        IFDEF   FARCALL
prep_swap       PROC    far pmethod: word, swapfname: dword
        ELSE
prep_swap       PROC    near pmethod: word, swapfname: dword
        ENDIF
        ELSE
        IFDEF   WATCOM
prep_swap       PROC    uses si di, pmethod: word, swapfname: dword
        ELSE
prep_swap       PROC    uses si di, pmethod: word, swapfname: ptr
        ENDIF
        ENDIF

        LOCAL   totparas: word
;
        IFDEF   TC_HUGE
        mov     ax,SEG my_data
        mov     ds,ax
        ENDIF
;
        IFDEF   PASCAL_DEF
        cld
        mov     ax,prefixseg
        ELSE
        IFDEF   TC_HUGE
        mov     ax,SEG _psp
        mov     es,ax
        mov     ax,es:_psp
        ELSE
        mov     ax,_psp
        ENDIF
        ENDIF
;
        dec     ax
        mov     prep.psp_mcb,ax
        mov     prep.first_mcb,ax       ; init first MCB to PSP
;
;       Make a copy of the environment pointer in the PSP
;
        inc     ax
        mov     es,ax
        mov     bx,es:psp_envptr
        dec     bx
        mov     prep.env_mcb,bx
        mov     prep.noswap_mcb,0
        test    pmethod,DONT_SWAP_ENV
        jz      can_swap_env
        mov     prep.noswap_mcb,bx
;
;       Check if spawn is too low in memory
;
can_swap_env:
        mov     bx,cs
        mov     dx,offset lowcode_begin
        mov     cl,4
        shr     dx,cl
        add     bx,dx                   ; normalized start of this code
        mov     dx,keep_paras           ; the end of the modified area
        add     dx,ax                   ; plus PSP = end paragraph
        cmp     bx,dx
        ja      prepswap_ok     ; ok if start of code > end of low mem
        mov     ax,-2
        mov     prep.swapmethod,al
        ret
;
;       Walk the chain of memory blocks, adding up the paragraphs
;       in all blocks belonging to this process.
;       We try to find the first MCB by getting DOS's "list of lists",
;       and fetching the word at offset -2 of the returned address.
;       If this fails, we use our PSP as the starting point.
;
prepswap_ok:
        xor     bx,bx
        mov     es,bx
        mov     ah,52h                  ; get list of lists
        int     21h
        mov     ax,es
        or      ax,bx
        jz      prep_no_first
        mov     es,es:[bx-2]            ; first MCB
        cmp     es:id,4dh               ; normal ID?
        jne     prep_no_first
        mov     prep.first_mcb,es
;
prep_no_first:
        mov     es,prep.psp_mcb         ; ES points to base MCB
        mov     cx,es                   ; save this value
        mov     bx,es:owner             ; the current process
        mov     dx,es:paras             ; memory size in the base block
        sub     dx,keep_paras           ; minus resident paragraphs
        mov     si,0                    ; number of MCBs except base
        mov     di,prep.noswap_mcb
        mov     ax,prep.first_mcb
        mov     prep.first_mcb,0
;
prep_mcb_walk:
        mov     es,ax
        cmp     ax,cx                   ; base block?
        je      prep_walk_next          ; then don't count again
        cmp     ax,di                   ; Non-swap MCB?
        je      prep_walk_next          ; then don't count
;
        cmp     bx,es:owner             ; our process?
        jne     prep_walk_next          ; next if not
        inc     si
        mov     ax,es:paras             ; else get number of paragraphs
        add     ax,2                    ; + 1 for descriptor + 1 for MCB
        add     dx,ax                   ; total number of paras
        cmp     prep.first_mcb,0
        jne     prep_walk_next
        mov     prep.first_mcb,es
;
prep_walk_next:
        cmp     es:id,4dh               ; normal block?
        jne     prep_mcb_ready          ; ready if end of chain
        mov     ax,es
        add     ax,es:paras             ; start + length
        inc     ax                      ; next MCB
        jmp     prep_mcb_walk
;
prep_mcb_ready:
        mov     totparas,dx
        mov     prep.total_mcbs,si
;
        test    pmethod,XMS_FIRST
        jnz     check_xms
;
;       Check for EMS swap
;
check_ems:
        test    pmethod,USE_EMS
        jz      prep_no_ems
;
        push    ds
        mov     al,EMM_INT
        mov     ah,35h
        int     21h                     ; get EMM int vector
        mov     ax,cs
        mov     ds,ax
        mov     si,offset emm_name
        mov     di,10
        mov     cx,8
        repz cmpsb                      ; EMM name present?
        pop     ds
        jnz     prep_no_ems
;
        mov     ah,40h                  ; get EMS status
        int     EMM_INT
        or      ah,ah                   ; EMS ok?
        jnz     prep_no_ems
;
        mov     ah,46h                  ; get EMS version
        int     EMM_INT
        or      ah,ah                   ; AH must be 0
        jnz     prep_no_ems
;
        cmp     al,30h                  ; >= version 3.0?
        jb      prep_no_ems
;
        mov     ah,41h                  ; Get page frame address
        int     EMM_INT
        or      ah,ah
        jnz     prep_no_ems
;
;       EMS present, try to allocate pages
;
        mov     prep.ems_pageframe,bx
        mov     bx,totparas
        add     bx,ems_paramask
        mov     cl,ems_shift
        shr     bx,cl
        mov     ah,43h                  ; allocate handle and pages
        int     EMM_INT
        or      ah,ah                   ; success?
        jnz     prep_no_ems
;
;       EMS pages allocated, swap to EMS
;
        mov     prep.handle,dx
        mov     ax,USE_EMS
        mov     prep.swapmethod,al
        ret
;
;       No EMS allowed, or EMS not present/full. Try XMS.
;
prep_no_ems:
        test    pmethod,XMS_FIRST
        jnz     check_file              ; don't try again
;
check_xms:
        test    pmethod,USE_XMS
        jz      prep_no_xms
;
        mov     ax,4300h                ; check if XMM driver present
        int     2fh
        cmp     al,80h                  ; is XMM installed?
        jne     prep_no_xms
        mov     ax,4310h                ; get XMM entrypoint
        int     2fh
        mov     word ptr prep.xmm,bx    ; save entry address
        mov     word ptr prep.xmm+2,es
;
        mov     dx,totparas
        add     dx,xms_paramask         ; round to nearest multiple of 1k
        mov     cl,xms_shift
        shr     dx,cl                   ; convert to k
        mov     ah,9                    ; allocate extended memory block
        call    dword ptr ds:[prep.xmm]
        or      ax,ax
        jz      prep_no_xms
;
;       XMS block allocated, swap to XMS
;
        mov     prep.handle,dx
        mov     ax,USE_XMS
        mov     prep.swapmethod,al
        ret
;
;       No XMS allowed, or XMS not present/full. Try File swap.
;
prep_no_xms:
        test    pmethod,XMS_FIRST
        jz      check_file
        jmp     check_ems
;
check_file:
        test    pmethod,USE_FILE
        jnz     prep_do_file
        jmp     prep_no_file
;
prep_do_file:
        push    ds
        IF      ptrsize
        lds     dx,swapfname
        ELSE
        mov     dx,swapfname
        ENDIF
        IFDEF   PASCAL_DEF
        inc     dx                      ; skip length byte
        ENDIF
        mov     cx,2                    ; hidden attribute
        test    pmethod,HIDE_FILE
        jnz     prep_hide
        xor     cx,cx                   ; normal attribute
;
prep_hide:
        mov     ah,3ch                  ; create file
        test    pmethod,CREAT_TEMP
        jz      prep_no_temp
        mov     ah,5ah
;
prep_no_temp:
        int     21h                     ; create/create temp
        jnc     prep_got_file
        jmp     prep_no_file
;
prep_got_file:
        mov     bx,ax                   ; handle
;
;       save the file name
;
        pop     es
        push    es
        mov     di,offset prep.swapfilename
        mov     cx,81
        mov     si,dx
        rep movsb
;
        pop     ds
        mov     prep.handle,bx
;
;       preallocate the file
;
        test    pmethod,NO_PREALLOC
        jnz     prep_noprealloc
        test    pmethod,CHECK_NET
        jz      prep_nonetcheck
;
;       check whether file is on a network drive, and don't preallocate
;       if so. preallocation can slow down swapping significantly when
;       running on certain networks (Novell)
;
        mov     ax,440ah        ; check if handle is remote
        int     21h
        jc      prep_nonetcheck ; assume not remote if function fails
        test    dh,80h          ; DX bit 15 set ?
        jnz     prep_noprealloc ; remote if yes
;
prep_nonetcheck:
        mov     dx,totparas
        mov     cl,4
        rol     dx,cl
        mov     cx,dx
        and     dx,0fff0h
        and     cx,0000fh
        sub     dx,1
        sbb     cx,0
        mov     si,dx                   ; save
        mov     ax,4200h                ; move file pointer, absolute
        int     21h
        jc      prep_file_err
        cmp     dx,cx
        jne     prep_file_err
        cmp     ax,si
        jne     prep_file_err
        mov     cx,1                    ; write 1 byte
        mov     ah,40h
        int     21h
        jc      prep_file_err
        cmp     ax,cx
        jne     prep_file_err
;
        mov     ax,4200h                ; move file pointer, absolute
        xor     dx,dx
        xor     cx,cx                   ; rewind to beginning
        int     21h
        jc      prep_file_err
;
prep_noprealloc:
        mov     ax,USE_FILE
        mov     prep.swapmethod,al
        ret
;
prep_file_err:
        mov     ah,3eh                  ; close file
        int     21h
        mov     dx,offset prep.swapfilename
        mov     ah,41h                  ; delete file
        int     21h
;
prep_no_file:
        mov     ax,-1
        mov     prep.swapmethod,al
        ret
;
prep_swap       endp
;
        end

