Metropoli BBS
VIEWER: spawn.asm MODE: TEXT (ASCII)
;FIX! : v2.02 : enviroment lookup was fixed
;FIX! : v2.05 : exec...() simply call program and quit as does BC so I no
;               longer consider this a bug

include qlib.inc
include process.inc
include string.inc
include dos.inc
include stdlib.inc
include alloc.inc
include errno.inc
include stdio.inc

;Spawn and all it's variants were hard to implement.
;Everything is supported.  

.data?
  dopath db ?  ;flag - search fn for program
  doenv db ?   ;flag - made new enviroment
  dot db ?     ;flag - a . is in fn
  bat db ?     ;flag - filename is a BAT file

  fn dd ?      ;filename to exec
  fnl dd ?     ;length of filename
  mode dw ?    ;from func  (P_WAIT=0 P_NOWAIT=1[error]  P_OVERLAY=2)
  arglen db ?               ;parameters len
  args db 128 dup (?)       ;parameters (arguments)
  ff ffblk <>
  path dd ?                 ;pts to %PATH% in the enviroment
  thepath db 128 dup (?)    ;a part of %PATH%
  totalpath db 128 dup (?)  ;path and filename
  tfnl dd ?                 ;total file name length
  envirohand dd ?           ;handle for enviro memory block (32k)

.data
  pbs struct                ;parameter block
    enviroff dd ?
    envirsel dw ?
    cto dd offset arglen    ;command tail
    cts dw ?
    fcb1 dd 0
    fcb2 dd 0
  pbs ends
  pb pbs <>

.code
findit proc private,p:dword,at:byte
  local len:dword
  ;looks for program "p+fn"
  ;if at==\\ then a \\ is not added between 'p' and 'fn'
  ;at is simply the last char of 'p'
  callp strlen,p
  mov ebx,eax
  add ebx,fnl
  mov len,ebx
  .if ebx>127
    mov eax,ERROR
    ret
  .endif
  mov ebx,offset totalpath
  callp strcpy,ebx,p
  .if at!='\'
    callp strcat,ebx,"\\"
    inc len
  .endif
  callp strcat,ebx,fn
  callp findfirst,ebx,0h,offset ff

  .if !eax
    ret     ;found it!
  .endif
;try and add .COM and .EXE (order is important to follow DOS implementation)
  .if len>(127-5)
    mov eax,ERROR
    ret           ;not enough room to add it
  .endif
  .if dot
    mov eax,ERROR
  .endif
    
tryit:  ;there is no . so we can add .COM and then .EXE
  mov esi,offset totalpath
  add esi,len
  mov len,esi  ;save for ...
  callp strcpy,esi,".COM"
  callp findfirst,offset totalpath,0h,offset ff
  .if !eax
    ret
  .endif       
  mov esi,len  ;... here
  callp strcpy,esi,".EXE"
  callp findfirst,offset totalpath,0h,offset ff
  ;EAX is returned here
  ret
findit endp

add_bat proc private
  ;adds "/C totalpath " in front of all ARGS (if possible)
  ;changes totalpath to %COMSPEC%

  callp strlen,offset totalpath
  mov tfnl,eax
  add eax,4
  xor ebx,ebx
  mov bl,arglen
  .if ebx>eax
    mov eax,ERROR
    ret
  .endif

  sub esp,128  ;reserve space for temp storage

  mov ebx,esp
  callp memcpy,ebx,offset args,arglen
  callp memcpy,offset args," /C ",4
  callp memcpy,offset args+4,offset totalpath,tfnl
  mov eax,esp
  mov ebx,offset args+4
  add ebx,tfnl
  callp memcpy,ebx,eax,arglen

  callp getenv,"COMSPEC"
  .if eax==NULL
    mov eax,ERROR
    add esp,128
    ret
  .endif
  callp strcpy,offset totalpath,eax

  mov eax,tfnl
  add eax,4
  add arglen,al

  xor eax,eax
  add esp,128
  ret
add_bat endp

_spawn proc private
  pushad
  .if mode == P_NOWAIT
    mov errno,EINVAL
    jmp bad
  .endif

;misc setup
  callp strlen,fn
  .if eax>127
    mov errno,ENOENT   ;path not found
    jmp bad
  .endif
  mov fnl,eax

  mov edi,fn
  mov ecx,eax
  mov al,'.'
  repnz scasb
  .if zero?  ;a . was found
    mov dot,1
  .else
    mov dot,0
  .endif
  callp memicmp,edi,"BAT",3
  .if !eax
    mov bat,1
  .else
    mov bat,0
  .endif

  .if !doenv
    ;use parent's enviro for child process
    mov eax,_environ
    mov pb.enviroff,eax
  .endif
  mov ax,seldata
  mov pb.envirsel,ax
  mov pb.cts,ax

;find child
  callp findit,"",'\'   ;make at='\' so that it will not add a '\'
  .if !eax
    jmp doit
  .endif
  mov ecx,fn
  .if !dopath
    mov errno,ENOENT
    jmp bad
  .endif

;search for '\' or ':'  (if any are found then we cannot cruz thru %path%)
  mov edi,fn
  mov ecx,fnl
  mov al,'\'
  repnz scasb
  .if zero?
    mov errno,ENOENT
    jmp bad
  .endif
  mov edi,fn
  mov ecx,fnl
  mov al,':'
  repnz scasb
  .if zero?
    mov errno,ENOENT
    jmp bad
  .endif
 
  callp getenv,"PATH"
  .if eax==NULL
    mov errno,ENOENT
    jmp bad
  .endif
  mov path,eax
tryagain:
  mov esi,path
  mov edi,offset thepath
  mov cl,1
@@:
  lodsb
  .if ((al==';') || (al==0))
    .if !al
      mov cl,0  ;Stop after this attempt
    .endif
    mov al,0
    stosb
    mov path,esi  ;save new pos
    jmp tryit
  .endif
  mov bl,al
  stosb
  .if edi>offset thepath+120    ;path too long
@@1:
    lodsb
    .if al==';'
      mov path,esi
      jmp tryagain
    .endif
    .if !al
      mov errno,ENOENT
      jmp bad
    .endif
    jmp @@1
  .endif
  jmp @b

tryit:
  push ebx
  push ecx
  callp findit,offset thepath,bl  ;does not preserve regs
  pop ecx
  pop ebx
  .if eax==ERROR
    .if !cl
      mov errno,ENOENT
      jmp bad
    .endif
    jmp tryagain
  .endif
doit:
  ;SPAWN! (or exec)  
  .if bat
    call add_bat
    .if eax==ERROR
      mov eax,ENOENT
      jmp bad
    .endif
  .endif
  .if mode==P_OVERLAY
    ;OK, exec is very tricky.
    ;PSP+ah is the terminate vector.  which must be changed to something
    ;that will load something and then go back to COMMAND.COM (or whatever)
    ;when we terminate (4ch)
    ;But what memory do we alloc (if we go thru DOS it will be unalloc as
    ;soon as we end program).  We need enough RAM to have a little code and the
    ;parameter block.  Any ideas?

    ;for now I will simply evec new program and then quit if successful
    mov ax,4b00h    ;load & exec program
    mov edx,offset totalpath
    mov ebx,offset pb
    int 21h
    .if carry?
      mov errno,ax
      jmp bad
    .endif
    callp exit,0   ;exit if successful
  .else
    mov ax,4b00h    ;load & exec program
    mov edx,offset totalpath
    mov ebx,offset pb
    int 21h
    .if carry?
      mov errno,ax
      jmp bad
    .endif
    mov ah,4dh   ;get error code of child
    int 21h
    xor ebx,ebx
    mov bl,al
    mov [esp+7*4],ebx  ;save as EAX
  .endif
  mov eax,envirohand
  .if eax
    callp free,eax
  .endif
  mov edx,_dta
  mov ah,1ah
  int 21h  ;reset DTA
  popad
  xor eax,eax
  ret
bad:
  mov edx,_dta
  mov ah,1ah
  int 21h  ;reset DTA
  mov eax,envirohand
  .if eax
    callp free,eax
  .endif
  popad
  mov eax,ERROR
  ret
_spawn endp

setarg proc private,va:dword  ;ret:eax=pt to next arg after NULL
  local len:dword
  pushad
  mov ebx,va
;  mov ecx,128/4           ;clear ARG area (not needed)
;  mov edi,offset args
;  xor eax,eax
;  rep stosd
  mov edx,[ebx]
  mov ecx,offset args
  mov bptr[ecx],' ' ;must insert a leading space
  inc ecx
  .while edx
    callp strlen,edx
    mov len,eax
    add eax,ecx
    .if eax>(126+offset args)  ;need a space after para too
      popad
      mov eax,ERROR
      mov errno,E2BIG
      ret
    .endif
    callp strcpy,ecx,edx
    add ecx,len
    mov bptr[ecx],' '
    inc ecx
    add ebx,4
    mov edx,[ebx]
  .endw
  add ebx,4  ;pt to next after NULL (in case the enviro array is after it)
  mov bptr[ecx],0
  sub ecx,offset args
  mov arglen,cl
  mov [esp+7*4],ebx  ;save as EAX
  popad
  ret
setarg endp

setenv proc private,e:dword
  local len:dword
  local max:dword
  ;setup an enviro block
  callp malloc,32*1024  ;MAX size of enviro block
  .if eax==NULL
    mov errno,ENOMEM
    mov eax,ERROR
    ret
  .endif
  mov pb.enviroff,eax
  mov envirohand,eax
  pushad
  mov ebx,e
  mov edx,[ebx]
  mov ecx,eax
  mov max,ecx
  add max,32*1024
  .while edx
    callp strlen,edx
    mov len,eax
    add eax,ecx
    add eax,2     ;FIX! v2.02 : we need room for the zero and double zero
    cmp eax,max
    ja full
    callp strcpy,ecx,edx
    add ecx,len
    inc ecx  ;skip over 0
    add ebx,4
    mov edx,[ebx]
  .endw
full:
  mov bptr[ecx],0 ;put last 0 (this is the double 0 thingy)
  popad
  xor eax,eax
  ret
setenv endp

;list of args passed
spawnl proc,m:word,tp:dword,va:vararg ;...,NULL
  mov eax,tp
  mov fn,eax
  mov ax,m
  mov mode,ax
  lea eax,va
  callp setarg,eax
  .if eax==ERROR
    ret
  .endif
  mov doenv,0  ;use parent's enviroment
  mov dopath,0
  mov envirohand,0
  call _spawn
  ret
spawnl endp

spawnle proc,m:word,tp:dword,va:vararg ;...,NULL,char *envp[]
  mov eax,tp
  mov fn,eax
  mov ax,m
  mov mode,ax
  lea eax,va
  callp setarg,eax
  .if eax==ERROR
    ret
  .endif
  mov eax,[eax]    ;get env
  callp setenv,eax
  .if eax==ERROR
    ret
  .endif
  mov doenv,1  ;use new enviroment
  mov dopath,0
  call _spawn
  ret
spawnle endp

spawnlp proc,m:word,tp:dword,va:vararg ;...,NULL
  mov eax,tp
  mov fn,eax
  mov ax,m
  mov mode,ax
  lea eax,va
  callp setarg,eax
  .if eax==ERROR
    ret
  .endif
  mov doenv,0  ;use parent's enviroment
  mov dopath,1
  mov envirohand,0
  call _spawn
  ret
spawnlp endp

spawnlpe proc,m:word,tp:dword,va:vararg ;...,NULL,char *envp[]
  mov eax,tp
  mov fn,eax
  mov ax,m
  mov mode,ax
  lea eax,va
  callp setarg,eax
  .if eax==ERROR
    ret
  .endif
  mov eax,[eax]    ;get env
  callp setenv,eax
  .if eax==ERROR
    ret
  .endif
  mov doenv,1  ;new enviroment
  mov dopath,1
  call _spawn
  ret
spawnlpe endp

; V variants follow

spawnv proc,m:word,tp:dword,va:dword
  mov eax,tp
  mov fn,eax
  mov ax,m
  mov mode,ax
  callp setarg,va
  .if eax==ERROR
    ret
  .endif
  mov doenv,0  ;use parent's enviroment
  mov dopath,0
  mov envirohand,0
  call _spawn
  ret
spawnv endp

spawnve proc,m:word,tp:dword,va:dword,e:dword
  mov eax,tp
  mov fn,eax
  mov ax,m
  mov mode,ax
  callp setarg,va
  .if eax==ERROR
    ret
  .endif
  callp setenv,e
  .if eax==ERROR
    ret
  .endif
  mov doenv,1  ;use parent's enviroment
  mov dopath,0
  call _spawn
  ret
spawnve endp

spawnvp proc,m:word,tp:dword,va:dword
  mov eax,tp
  mov fn,eax
  mov ax,m
  mov mode,ax
  callp setarg,va
  .if eax==ERROR
    ret
  .endif
  mov doenv,0  ;use parent's enviroment
  mov dopath,1
  mov envirohand,0
  call _spawn
  ret
spawnvp endp

spawnvpe proc,m:word,tp:dword,va:dword,e:dword
  mov eax,tp
  mov fn,eax
  mov ax,m
  mov mode,ax
  callp setarg,va
  .if eax==ERROR
    ret
  .endif
  callp setenv,e
  .if eax==ERROR
    ret
  .endif
  mov doenv,1  ;use parent's enviroment
  mov dopath,1
  call _spawn
  ret
spawnvpe endp

;list of args passed
execl proc,tp:dword,va:vararg ;...,NULL
  mov eax,tp
  mov fn,eax
  lea eax,va
  callp setarg,eax
  .if eax==ERROR
    ret
  .endif
  mov doenv,0  ;use parent's enviroment
  mov dopath,0
  mov mode,P_OVERLAY
  mov envirohand,0
  call _spawn
  ret
execl endp

execle proc,tp:dword,va:vararg ;...,NULL,char *envp[]
  mov eax,tp
  mov fn,eax
  lea eax,va
  callp setarg,eax
  .if eax==ERROR
    ret
  .endif
  mov eax,[eax]    ;get env
  callp setenv,eax
  .if eax==ERROR
    ret
  .endif
  mov doenv,1  ;use new enviroment
  mov dopath,0
  mov mode,P_OVERLAY
  call _spawn
  ret
execle endp

execlp proc,tp:dword,va:vararg ;...,NULL
  mov eax,tp
  mov fn,eax
  lea eax,va
  callp setarg,eax
  .if eax==ERROR
    ret
  .endif
  mov doenv,0  ;use parent's enviroment
  mov dopath,1
  mov mode,P_OVERLAY
  mov envirohand,0
  call _spawn
  ret
execlp endp

execlpe proc,tp:dword,va:vararg ;...,NULL,char *envp[]
  mov eax,tp
  mov fn,eax
  lea eax,va
  callp setarg,eax
  .if eax==ERROR
    ret
  .endif
  mov eax,[eax]    ;get env
  callp setenv,eax
  .if eax==ERROR
    ret
  .endif
  mov doenv,1  ;new enviroment
  mov dopath,1
  mov mode,P_OVERLAY
  call _spawn
  ret
execlpe endp

; V variants follow

execv proc,tp:dword,va:dword
  mov eax,tp
  mov fn,eax
  callp setarg,va
  .if eax==ERROR
    ret
  .endif
  mov doenv,0  ;use parent's enviroment
  mov dopath,0
  mov mode,P_OVERLAY
  mov envirohand,0
  call _spawn
  ret
execv endp

execve proc,tp:dword,va:dword,e:dword
  mov eax,tp
  mov fn,eax
  callp setarg,va
  .if eax==ERROR
    ret
  .endif
  callp setenv,e
  .if eax==ERROR
    ret
  .endif
  mov doenv,1  ;use parent's enviroment
  mov dopath,0
  mov mode,P_OVERLAY
  call _spawn
  ret
execve endp

execvp proc,tp:dword,va:dword
  mov eax,tp
  mov fn,eax
  callp setarg,va
  .if eax==ERROR
    ret
  .endif
  mov doenv,0  ;use parent's enviroment
  mov dopath,1
  mov mode,P_OVERLAY
  mov envirohand,0
  call _spawn
  ret
execvp endp

execvpe proc,tp:dword,va:dword,e:dword
  mov eax,tp
  mov fn,eax
  callp setarg,va
  .if eax==ERROR
    ret
  .endif
  callp setenv,e
  .if eax==ERROR
    ret
  .endif
  mov doenv,1  ;use parent's enviroment
  mov dopath,1
  mov mode,P_OVERLAY
  call _spawn
  ret
execvpe endp

end

[ RETURN TO DIRECTORY ]