; FILE: Source:run.ASM          REV: 12 --- run with detach
; History
;  0      started 5th Jan 2000. loosely based on kickstart 40.68
;         internal run.
;  1      works. DETACH/S and QUIET/S not yet functional.
;  2      wrote forpath & findcommand for DETACH/S.
;  3      wrote detach routine for DETACH/S. Added STACK/K/N and
;         PRI/K/N (work for non-detach mode too).
;  4      6th Jan 2000: DETACH/S: worked a bit on ReadItem() problem,
;         now it finally works as expected. Made QUIET/S work.
;  5      DETACH/S: added proper ioerr to findcommand failure, added
;         consistent error output, uses undocumented negative ioerr
;         with PrintFault().
;  6      DETACH/S: won't load executable with E bit clear, now
;         findcommand should find the command 100% the same as
;         amigaos shell.
;  7      DETACH/S: removed Forbid() + FreeVec() hack from pr_ExitCode,
;         uses AllocEntry() + TC_MEMLIST instead.
;  8      7th Jan 2000: now DETACH/S creates complete fake CLI
;         structure.
;  9      now DETACH/S always adds exitcode to process because it also
;         clears pr_Flags PRF_FREESEGLIST if pr_CLI.cli_Module gets
;         cleared by command. this prevents crash if command has
;         autodetach feature built in. nice.
;  10     DETACH/S exitcode: hmm, apparently clearing PRF_FREESEGLIST
;         is not enough, now also clears item 3 from pr_SegList array.
;         now seems to work properly. froze 44.1.beta1.
;  11     8th Jan 2000: DETACH/S used to copy PROGDIR: of `run' itself,
;         caused missing PROGDIR: if `run' was made resident. Now sets
;         it properly. As a side effect had to create better API for
;         findcommand, now uses structure to pass things around.
;  12     27th Jan 2000: DETACH/S failed to create PROGDIR: correctly
;         if programname had path components. Fixed & changed
;         findcommand API a bit. froze 44.1 aminet release.
;

; installation:
;
; protect source:run prwed
; resident run remove
; resident source:run add

; defaults
MIN_STACK	EQU	512			; minimum stack allowed
MAX_PRI	EQU	127			; maximum priority allowed
MIN_PRI	EQU	-128			; minimum priority allowed
DIR_LEN	EQU	80			; maximum cli_SetName len
COMMNAME_LEN	EQU	104			; maximum cli_CommandName len
PROMPT_LEN	EQU	60			; maximum cli_Prompt len

SUBQ1ABS_OPCODE	EQU	$53B9			; subq.l #1,(abs).l


; flags for conditional compiling
USE_TC_MEMLIST	EQU	1


	include	"exec/lists.i"
	include	"exec/libraries.i"
	include	"exec/memory.i"
	include	"dos/dos.i"
	include	"dos/dosextens.i"
	include	"dos/rdargs.i"
	include	"dos/dostags.i"

	include	"exec/exec_lib.i"
	include	"dos/dos_lib.i"

call	MACRO
	jsr	(_LVO\1,a6)
	ENDM


	STRUCTURE stackdata,0
	APTR	sd_execbase
	APTR	sd_dosbase
	APTR	sd_rdargs
	LABEL	sd_args
	LONG	sd_detach
	LONG	sd_quiet
	LONG	sd_stack
	LONG	sd_pri
	APTR	sd_command
	ALIGNLONG
	LABEL	sd_SIZEOF


; ---> detach routine

	BITDEF	DETACH,USENIL,0

; <--- detach routine


; ---> findcommand routine

	STRUCTURE fcmd,0
	BPTR	fcmd_seglist		; in: 0 out: seglist
	LABEL	fcmd_nameptr		; in: char *
	BPTR	fcmd_homedir		; out: BPTR
	LONG	fcmd_subqpos		; in: 0 out: &seg_UC if res, else 0
	APTR	fcmd_internal		; internal stuff
	LABEL	fcmd_SIZEOF

; <--- findcommand routine



Main	movem.l	d2-d3/d6-d7/a3-a6,-(sp)

	moveq	#sd_SIZEOF/4,d0
.clr	clr.l	-(sp)
	subq.l	#1,d0
	bne.b	.clr
	move.l	sp,a5

	move.l	(4).w,a6
	moveq	#RETURN_ERROR,d7
	move.l	a6,(a5)

	lea	(dosname,pc),a1
	moveq	#39,d0
	call	OpenLibrary
	move.l	d0,(sd_dosbase,a5)
	beq	.cleanup

	move.l	(sd_dosbase,a5),a6

	lea	(template,pc),a0
	move.l	a0,d1
	lea	(sd_args,a5),a0
	move.l	a0,d2
	moveq	#0,d3
	call	ReadArgs
	move.l	d0,(sd_rdargs,a5)
	bne.b	.argsok

	call	IoErr
	move.l	d0,d1
	moveq	#0,d2
	call	PrintFault
	bra	.cleanup
.argsok

	; check priority validity:
	move.l	(sd_pri,a5),d0
	beq.b	.priok
	move.l	d0,a0
	move.l	(a0),d0
	cmp.l	#MAX_PRI,d0
	ble.b	.nottoohigh
	moveq	#MAX_PRI,d0
.nottoohigh	cmp.l	#MIN_PRI,d0
	bge.b	.nottoolow
	moveq	#MIN_PRI,d0
.nottoolow	move.l	d0,(a0)
.priok
	; check stack validity:
	move.l	(sd_stack,a5),d0
	beq.b	.stackok
	move.l	d0,a0
	move.l	(a0),d0
	cmp.l	#MIN_STACK,d0
	bge.b	.stackok
	move.l	#MIN_STACK,(a0)
.stackok

	; `old way' run or detach?
	tst.l	(sd_detach,a5)
	bne	.detach

	; this code fragment builds fake filehandle
	; that feeds System() with COMMAND/F argument.
	; this is the way amigaos does it. hacky ain't it?
	;
	; System() will free memory allocated...
	; could maybe use AllocDosObject DOS_FILEHANDLE ?
	;

	move.l	(sd_command,a5),a4
	move.l	a4,d0
	bne.b	.cmdok
	lea	(nullstr,pc),a4
.cmdok
	moveq	#-1,d6
	move.l	a4,a0
.len	addq.l	#1,d6
	tst.b	(a0)+
	bne.b	.len

	moveq	#fh_SIZEOF+14+2,d0
	add.l	d6,d0
	move.l	#MEMF_PUBLIC|MEMF_CLEAR,d1
	move.l	(a5),a6
	call	AllocVec
	tst.l	d0
	beq	.cleanup

	move.l	d0,a3
	lea	(fh_SIZEOF,a3),a0
	move.l	a0,d0
	asr.l	#2,d0
	move.l	d0,(fh_Buf,a3)

	move.l	d6,d0
	beq.b	.nocopy
.bcopy	move.b	(a4)+,(a0)+
	subq.l	#1,d0
	bne.b	.bcopy
.nocopy
	move.b	#10,(a0)+
	move.b	#10,(a0)

	addq.l	#2,d6
	move.l	a3,d2
	move.l	d6,(fh_End,a3)

	move.l	(sd_dosbase,a5),a6
	asr.l	#2,d2

	move.l	sp,a2

	clr.l	-(sp)
	pea	(1).w
	pea	SYS_UserShell
	call	Output
	move.l	d0,-(sp)
	pea	SYS_Output

	move.l	d2,-(sp)
	pea	SYS_Input

	move.l	(sd_stack,a5),d0
	beq.b	.nostack
	move.l	d0,a0
	move.l	(a0),-(sp)
	pea	NP_StackSize
.nostack
	move.l	(sd_pri,a5),d0
	beq.b	.nopri
	move.l	d0,a0
	move.l	(a0),-(sp)
	pea	NP_Priority
.nopri
	moveq	#0,d1
	move.l	sp,d2
	call	SystemTagList
	move.l	a2,sp

	move.l	d0,d7
	addq.l	#1,d0
	bne.b	.cleanup

	moveq	#RETURN_ERROR,d7

.cleanup	move.l	(sd_dosbase,a5),d0
	beq.b	.nodos
	move.l	d0,a6
	move.l	(sd_rdargs,a5),d1
	beq.b	.no_freeargs
	call	FreeArgs
.no_freeargs	move.l	(sd_dosbase,a5),a1
	move.l	(a5),a6
	call	CloseLibrary
.nodos	lea	(sd_SIZEOF,sp),sp
	move.l	d7,d0
	movem.l	(sp)+,d2-d3/d6-d7/a3-a6
	rts


.detach	move.l	#$80000000,d1
	move.l	(sd_pri,a5),d0
	beq.b	.use_defpri
	move.l	d0,a0
	move.l	(a0),d1
.use_defpri
	move.l	(sd_stack,a5),d2
	beq.b	.use_defstack
	move.l	d2,a0
	move.l	(a0),d2
.use_defstack
	moveq	#0,d0
	tst.l	(sd_quiet,a5)
	beq.b	.noquiet
	moveq	#DETACHF_USENIL,d0
.noquiet
	move.l	(sd_command,a5),a0
	bsr.b	detach			; has internal check for null a0
	tst.l	d0
	beq.b	.cleanup
	moveq	#RETURN_OK,d7
	bra.b	.cleanup


; detach - run given command as background process with fake cli
; structure.
;
;  IN: a0=char *commandline (will be modified)
;      a6=dosbase
;      d0=flags
;      d1=priority, $80000000 = use default
;      d2=stacksize, 0 = use default
; OUT: d0=success
;
detach	movem.l	d0-d7/a0-a6,-(sp)
	move.l	d0,d6
	move.l	sp,a5
	clr.l	(sp)
	move.l	(4).w,d7

	move.l	a0,d0			; null commandline?
	beq	.exit			; eek, quit!
	tst.b	(a0)
	beq	.exit			; eek, quit!

	moveq	#0,d4
	moveq	#0,d5
	IFNE	USE_TC_MEMLIST
	move.w	#-1,a4
	ELSE
	sub.l	a4,a4
	ENDC

	; get commandline len
	move.l	(8*4,sp),a0		; get a0 from stack
	moveq	#-1,d0
.len	addq.l	#1,d0
	tst.b	(a0)+
	bne.b	.len

	; build CSource structure to stack
	lea	(-COMMNAME_LEN,sp),sp	; storage for commandname
	clr.b	(sp)			; nullterminate! (for .argfail)
	move.l	sp,d1			; buffer
	clr.l	-(sp)			; CS_CurChr
	move.l	d0,-(sp)		; CS_Length
	move.l	(8*4,a5),-(sp)		; CS_Buffer (get a0 from stack)

	moveq	#COMMNAME_LEN-1,d2	; maxchars
	move.l	sp,d3			; struct CSource *
	call	ReadItem

	moveq	#0,d3			; d3=homedir lock ptr!

	move.l	(sp)+,a0		; CS_Buffer
	move.l	(sp)+,d1		; CS_Length
	add.l	(sp)+,a0		; add CS_CurChr

	tst.l	d0
	bmi	.argfail

	; skip some chars if needed, needed because ReadItem()
	; does UnGetC... stupid.

	subq.l	#2,d1			; must have at least one char
	bmi.b	.singlearg
	cmp.b	#' ',(-1,a0)
	beq.b	.noskip
	addq.l	#1,a0
	bra.b	.noskip
.singlearg	tst.b	(a0)+			; make a0 point nullchar
	bne.b	.singlearg
	subq.l	#1,a0
.noskip

	; a0 = after first param = command arguments.
	; quite neat eh? OS rules ;)

	; lf + null terminate a0 string,
	; this is really needed, NP_Arguments &
	; ReadArgs() require this
	move.l	(8*4,a5),a1		; get a0 from stack
.copy	move.b	(a0)+,(a1)+
	bne.b	.copy
	move.b	#10,(-1,a1)
	move.b	#0,(a1)

	; if USENIL flag is set don't open console:
	; filehandles, but use default nil: i/o

	btst	#DETACHB_USENIL,d6
	bne.b	.use_nil1

	lea	(.conname,pc),a2
	move.l	a2,d1
	move.l	#MODE_NEWFILE,d2
	call	Open
	move.l	d0,d5
	beq	.filefail
	move.l	a2,d1
	;move.l	#MODE_NEWFILE,d2
	call	Open
	move.l	d0,d4
	beq	.filefail
.use_nil1

	; build taglist for CreateNewProc to stack

	clr.l	-(sp)
	move.l	(8*4,a5),-(sp)		; get a0 from stack
	pea	NP_Arguments
	pea	(-COMMNAME_LEN,a5)	; commandname
	pea	NP_Name

	; find command seglist, uses findcommand subroutine

	; build findcommand structure to stack
	subq.l	#4,sp			; fcmd_internal
	clr.l	-(sp)			; fcmd_subqpos
	pea	(-COMMNAME_LEN,a5)	; in: fcmd_nameptr / out: fcmd_homedir
	clr.l	-(sp)			; fcmd_seglist
	move.l	sp,a0
	bsr	findcommand
	move.l	(sp)+,a3		; get fcmd_seglist
	move.l	(sp)+,d1		; get fcmd_homedir
	move.l	(sp)+,a2		; get fcmd_subqpos
	addq.l	#4,sp			; pop fcmd_internal

	tst.l	d0
	beq	.nocommand

	move.l	d1,d3			; d1=fcmd_homedir

	; make PROGDIR: work...
	move.l	d3,-(sp)
	pea	NP_HomeDir

	; if the command found was (not internal) resident we
	; must prepare ourselves to decrement its seg_UC
	; after executing it. this requires some hacking
	; namely use of NP_ExitCode to pass code to be run
	; after program termination and some way to free the
	; memory fragment allocated for that code.
	;
	; there are two ways to handle the memory freeing,
	; either Forbid() & free the memory in exitcode
	; itself or use task TCB's TC_MEMLIST to free it.
	; latter is more OS legal, but requires sorrounding
	; CreateNewProc() with Forbid()/Permit() pair.
	;
	; but then, we need to forbid there anyways, because
	; of pr_CLI faking so USE_TC_MEMLIST method is better.
	;
	; this exitcode also handles cli_Module=NULL seglist
	; cutting, this prevents potential problems if
	; command has some autodetach logic built in.

	exg	d7,a6

	IFNE	USE_TC_MEMLIST

	; build MemEntry to stack
	pea	(exitcode_SIZEOF).w
	pea	(MEMF_PUBLIC).w		Entry #0
	pea	(1).w			Number of entries (longword zeropadded)
	lea	(-(LN_SIZE-2),sp),sp	Reserve space for list node (-2 = padding...)
	move.l	sp,a0
	call	AllocEntry
	lea	((LN_SIZE-2)+(3*4),sp),sp
	exg	d7,a6
	tst.l	d0
	bmi	.nomem

	move.l	d0,a4
	move.l	(ML_ME+0*ME_SIZE,a4),a1

	ELSE

	moveq	#exitcode_SIZEOF,d0
	moveq	#MEMF_PUBLIC,d1
	call	AllocVec
	exg	d7,a6
	tst.l	d0
	beq	.nomem
	move.l	d0,a4
	move.l	d0,a1

	ENDC

	; copy the exitcode to newly allocated memory:

	exg	d7,a6
	lea	(.exitcode,pc),a0
	moveq	#exitcode_SIZEOF,d0
	call	CopyMem

	; if we need to decrement seg_UC add subq instruction:
	move.l	a2,d0
	beq.b	.no_subqinst
	IFNE	USE_TC_MEMLIST
	move.l	(ML_ME+0*ME_SIZE,a4),a0
	;lea	(.subqaddr-.exitcode,a0),a0
	ELSE
	;lea	(.subqaddr-.exitcode,a4),a0
	move.l	a4,a0
	ENDC
	move.w	#SUBQ1ABS_OPCODE,(a0)+
	move.l	a2,(a0)
.no_subqinst

	; we just built code to be executed so better clear
	; CPU caches...
	call	CacheClearU
	exg	d7,a6

	; stuff exitcode
	IFNE	USE_TC_MEMLIST
	move.l	(ML_ME+0*ME_SIZE,a4),-(sp)
	ELSE
	move.l	a4,-(sp)
	ENDC
	pea	NP_ExitCode


	; ask OS to free the seglist when done (default)

	pea	(1).w
	pea	NP_FreeSeglist

	; pass the seglist pointer

	move.l	a3,-(sp)
	bpl.b	.segptok
	; seglist pointer was negative so we must negate it to
	; get real pointer. this also indicates that this seglist
	; should not be unloadseg'd when done so clear ti_Data of
	; NP_FreeSeglist tag.
	neg.l	(sp)
	clr.l	(8,sp)			; clear ti_Data of NP_FreeSeglist
.segptok	pea	NP_Seglist


	; if we were given a priority, stuff tag indicating it

	move.l	(1*4,a5),d0		; get d1 from stack
	cmp.l	#$80000000,d0
	beq.b	.use_defpri
	move.l	d0,-(sp)
	pea	NP_Priority
.use_defpri

	; put NP_StackSize tag.
	; if d2 was <>0 then use that value as stack size
	move.l	(2*4,a5),-(sp)		; get d2 from stack
	bne.b	.notcli
	; so d2 was zero then, lets try to peek out default
	; value to use. strategy:
	; 1) if CLI process get cli_DefaultStack
	; 2) not CLI process: use pr_StackSize or 4000,
	;    whichever is larger
	exg	d7,a6
	sub.l	a1,a1
	call	FindTask
	exg	d7,a6
	move.l	d0,a0
	move.l	(pr_StackSize,a0),d0
	cmp.l	#4000,d0
	bhs.b	.prstackok
	move.l	#4000,d0
.prstackok	move.l	d0,(sp)
	call	Cli
	tst.l	d0
	beq.b	.notcli
	move.l	d0,a0
	move.l	(cli_DefaultStack,a0),d0
	lsl.l	#2,d0			; must *4
	move.l	d0,(sp)
.notcli	pea	NP_StackSize

	; put NP_Output and NP_Input tags, unless USENIL flag
	btst	#DETACHB_USENIL,d6
	bne.b	.use_nil2
	move.l	d5,-(sp)
	pea	NP_Output
	move.l	d4,-(sp)
	pea	NP_Input
.use_nil2

	; allocate the fake CLI structure
	clr.l	-(sp)
	pea	(DIR_LEN).w
	pea	ADO_DirLen		; size in bytes for current dir buffer
	pea	(COMMNAME_LEN).w
	pea	ADO_CommNameLen		; size in bytes for command name buffer
	pea	(PROMPT_LEN).w
	pea	ADO_PromptLen		; size in bytes for the prompt buffer
	move.l	sp,d2
	moveq	#DOS_CLI,d1
	call	AllocDosObject
	lea	(7*4,sp),sp		; pop stack
	move.l	d0,d2
	beq	.nocli


	; also, for completeness, we need to poke in some
	; standard values to CLI structure. this ensures
	; that program using them will not choke.

	movem.l	d2-d3/a2,-(sp)		; must store d2-d3/a2 for .procfail!
	move.l	d2,a2
	lea	(-DIR_LEN,sp),sp	; temp string storage

	; set cli_SetName:
	move.l	sp,d1
	moveq	#DIR_LEN-1,d3
	call	GetCurrentDirName
	move.l	sp,a0
	move.l	(cli_SetName,a2),a1
	bsr	.cstr2bstr

	; set cli_CommandName:
	lea	(-COMMNAME_LEN,a5),a0
	move.l	(cli_CommandName,a2),a1
	bsr	.cstr2bstr

	; copy parent's prompt:
	move.l	sp,d1
	moveq	#PROMPT_LEN-1,d2
	call	GetPrompt
	move.l	sp,a0
	move.l	(cli_Prompt,a2),a1
	bsr	.cstr2bstr

	;cli_CommandFile will be nullstr.

	; default defaultstack :)
	move.l	#4096/4,(cli_DefaultStack,a2)
	; copy parent's cli_DefaultStack & cli_FailLevel:
	call	Cli
	tst.l	d0
	beq.b	.noparent
	move.l	d0,a0

	move.l	(cli_DefaultStack,a0),(cli_DefaultStack,a2)
	move.l	(cli_FailLevel,a0),(cli_FailLevel,a2)

	; copy parent's cli_CommandDir
	move.l	(cli_CommandDir,a0),d0
	bsr	copycommanddir
	move.l	d0,(cli_CommandDir,a2)
.noparent
	; set cli_Module:
	move.l	a3,(cli_Module,a2)
	bpl.b	.segptok2
	neg.l	(cli_Module,a2)
.segptok2

	lea	(DIR_LEN,sp),sp		; pop temp string storage
	movem.l	(sp)+,d2-d3/a2


	; must forbid because we change newly created
	; process before it will be run.
	exg	d7,a6
	call	Forbid
	exg	d7,a6

	; create the process!
	move.l	sp,d1
	call	CreateNewProc

	; if failed, get out and free everything we
	; allocated.
	tst.l	d0
	beq.b	.procfail

	; if we're using USE_TC_MEMLIST exitcode strategy
	; we must now link it to task's TC_MEMENTRY in order
	; it to get FreeEntry()'d on RemTask().

	move.l	d0,a2			; can trash a2 here

	IFNE	USE_TC_MEMLIST
	lea	(TC_MEMENTRY,a2),a0
	move.l	a4,a1
	ADDHEAD
	ENDC

	; now we only need to poke in the fake CLI structure
	; for the process (set BPTR(cli) to pr_CLI) and ask
	; process to free it when it will exit (set
	; PRF_FREECLI in pr_Flags).

	move.l	d2,a0

	; set cli_StandardInput & cli_CurrentInput:
	move.l	(pr_CIS,a2),(cli_StandardInput,a0)
	move.l	(pr_CIS,a2),(cli_CurrentInput,a0)

	; set cli_CurrentOutput & cli_StandardOutput:
	move.l	(pr_COS,a2),(cli_StandardOutput,a0)
	move.l	(pr_COS,a2),(cli_CurrentOutput,a0)

	lsr.l	#2,d2			; APTR->BPTR
	move.l	d2,(pr_CLI,a2)		; set pr_CLI
	or.w	#PRF_FREECLI,(pr_Flags+2,a2)	; set PRF_FREECLI


	exg	d7,a6
	call	Permit			; let it run!

	addq.l	#1,(a5)			; return success

.exit	move.l	a5,sp
	movem.l	(sp)+,d0-d7/a0-a6
	rts


.procfail	exg	d7,a6
	call	Permit
	exg	d7,a6
	moveq	#4,d1			;DOS_CLI
	;move.l	d2,d2
	call	FreeDosObject

.nocli
	IFNE	USE_TC_MEMLIST
	move.l	a4,d0
	bmi.b	.nomem
	exg	d7,a6
	move.l	a4,a0
	call	FreeEntry
	ELSE
	move.l	a4,d0
	beq.b	.nomem
	exg	d7,a6
	move.l	a4,a1
	call	FreeVec
	ENDC
	exg	d7,a6
.nomem
	move.l	a2,d0
	beq.b	.nosub
	subq.l	#1,(a2)			; decrement seg_UC
.nosub	move.l	a3,d1			; unload the segment
	bmi.b	.nounload		; *only* when needed
	call	UnLoadSeg
.nounload

.nocommand
.filefail
.argfail	call	IoErr
	move.l	d0,d2
	move.l	d3,d1
	beq.b	.nol
	call	UnLock
.nol	move.l	d4,d1
	beq.b	.noc1
	call	Close
.noc1	move.l	d5,d1
	beq.b	.noc2
	call	Close
.noc2
	cmp.l	#ERROR_OBJECT_NOT_FOUND,d2
	bne.b	.othererr

	; uses undocumented feature of PrintFault() here.
	; if you give it negative value it outputs some
	; predefined string from internal table.
	; locale.library will patch this table with current
	; language's strings. this way we stay 100% language
	; independent and don't need separate locale support.

	moveq	#-121,d1		; "Unknown command" (in english)
	lea	(-COMMNAME_LEN,a5),a0
	move.l	a0,d2
	bra.b	.pfault
.othererr
	move.l	d2,d1
	moveq	#0,d2
.pfault	call	PrintFault
	bra.b	.exit


	; copy str to bstr & fix bstr len
.cstr2bstr	move.l	a2,d0
	add.l	a1,a1
	add.l	a1,a1
	move.l	a1,a2
	st	(a1)+			; put -1 to count
.cs2bsl	addq.b	#1,(a2)			; increment count
	move.b	(a0)+,(a1)+		; copy chars, until zero
	bne.b	.cs2bsl
	move.l	d0,a2
	rts


;  IN: d0=rc
;      d1=exitdata
;      a6=execbase (documented?)
; OUT: d0=rc

.exitcode
.subqaddr	dc.w	$6004,$dead,$c0de
	movem.l	d0/a6,-(sp)
	move.l	(4).w,a6

	sub.l	a1,a1
	call	FindTask
	move.l	d0,a1

	; pr_CLI can be null if someone has
	; released it. we must prepare.
	move.l	(pr_CLI,a1),d0
	beq.b	.ec_nocli
	lsl.l	#2,d0
	move.l	d0,a0
	tst.l	(cli_Module,a0)
	bne.b	.ec_gotmodule
	and.w	#~PRF_FREESEGLIST,(pr_Flags+2,a1)
	move.l	(pr_SegList,a1),d0	; get BPTR to array of seglist used...
	lsl.l	#2,d0			; BPTR->APTR
	move.l	d0,a0
	clr.l	(12,a0)			; really prevent unloading!
.ec_gotmodule
.ec_nocli
	IFEQ	USE_TC_MEMLIST
	call	Forbid			; we'll free ourself...

	lea	(.exitcode,pc),a1	; set us free !-)
	call	FreeVec			; return + run rest of the code
					; in Forbid() state. RemTask(0)
					; will end this soon... :)
	ENDC
	movem.l	(sp)+,d0/a6
	rts
exitcode_SIZEOF	EQU	*-.exitcode

.conname	dc.b	'console:',0
	CNOP	0,2


; copycommanddir - create copy of cli_CommandDir
;
;  IN: d0=BPTR commanddir
;      a6=dosbase
; OUT: d0=BPTR newcommanddir
;
copycommanddir	movem.l	d7/a3-a6,-(sp)
	clr.l	-(sp)
	move.l	(4).w,d7

	move.l	d0,a5
	move.l	sp,a4
	bra.b	.gocopy

.copy	exg	d7,a6
	moveq	#8,d0
	moveq	#MEMF_PUBLIC,d1
	call	AllocVec
	tst.l	d0
	beq.b	.exit
	exg	d7,a6
	move.l	d0,a3
	move.l	(4,a5),d1		; from->lock
	call	DupLock
	move.l	a3,d1
	lsr.l	#2,d1
	move.l	d1,(a4)			; pred->succ=BPTR(this)
	move.l	a3,a4			; pred=this
	clr.l	(a3)+			; this->succ=NULL
	move.l	d0,(a3)			; this->lock=lock
	move.l	(a5),a5			; from=from->succ
.gocopy	add.l	a5,a5			; from=APTR(from)
	add.l	a5,a5
	move.l	a5,d0
	bne.b	.copy
.exit	movem.l	(sp)+,d0/d7/a3-a6
	rts


	STRUCTURE data,0
	APTR	d_seglistptr
	LABEL	d_nameptr
	APTR	d_homedir
	APTR	d_subqpos
	APTR	d_fib
	LABEL	data_SIZEOF


; findcommand - find command and return its seglist, scans resident
; list first, then shell path(s).
;
;  IN: a0=struct fcmd *findcommand,
;         fcmd_seglist  = 0
;         fcmd_nameptr  = char *commandname, command name to find
;         fcmd_subqpos  = 0
;      a6=dosbase
; OUT: d0=success, if succeeded structure passed in is filled:
;      a0=struct fcmd *findcommand,
;         fcmd_seglist  = BPTR seglist, if positive call UnLoadSeg
;                         for it when done.
;         fcmd_homedir  = BPTR lock, MUST be UnLock()ed when done!
;                         NULL for resident cmds.
;         fcmd_subqpos  = ULONG *subpos, if <>0 must decrement 1 from
;                         longword pointed when command has finished
;                         (this is resident command usecount).
;
;      NOTE: if findcommand fails, all fields are zeroed and ioerr
;      will be set to ERROR_OBJECT_NOT_FOUND.
;
findcommand	movem.l	d2-d7/a0/a2-a6,-(sp)
	move.l	a0,a5
	move.l	(4).w,d7

	; we need FileInfoBlock for examine...
	moveq	#DOS_FIB,d1
	moveq	#0,d2
	call	AllocDosObject
	move.l	d0,(d_fib,a5)
	beq	.nofib


	; first check if command has any path elements
	; in its name. if it has then split directory and
	; file names, and try to load the command.
	; If succeeds, progdir is the PathPart() of the
	; path.

	move.l	(d_nameptr,a5),d2
	move.l	d2,d1
	call	PathPart
	cmp.l	d0,d2
	beq.b	.nofix

	; ok. it does have path elements.

	move.l	d0,a2

	move.l	d0,d1
	call	FilePart
	move.l	(d_nameptr,a5),d4
	move.l	d0,(d_nameptr,a5)

	move.b	(a2),d6
	clr.b	(a2)			; cut filename
	move.l	d4,d1
	moveq	#ACCESS_READ,d2		; note: d2<>0 !
	call	Lock
	move.b	d6,(a2)			; fix filename
	move.l	d0,d3
	beq.b	.nopath

	; d0=lock, a1=data, a6=dosbase
	move.l	a5,a1
	bsr	.findroutine
	move.l	d0,d2

	; cleanup...
	move.l	d3,d1
	call	UnLock

	; don't try to fix d_nameptr here!

	tst.l	d2
	beq	.exit			; found it! quit

.nopath
	move.l	d4,(d_nameptr,a5)
.nofix

	; try to find command from resident list


	exg	d7,a6
	call	Forbid
	exg	d7,a6

	; first try system segments...
	moveq	#1,d3			; system segments
	bsr.b	.findseg

	tst.l	(a5)			; found seglist?
	bne.b	.foundit		; yeah, exit

	; we didn't find it from system segment list, try
	; normal segment list then...

	moveq	#0,d3			; normal segments
	bsr.b	.findseg
.foundit
	exg	d7,a6
	call	Permit
	exg	d7,a6

	tst.l	(a5)			; found seglist?
	bne.b	.exit			; yeah, exit

	; ok, so we could not find command from resident list,
	; lets try to find it somewhere within shell path(s)...
	; (uses forpath subroutine here)

	lea	(.findroutine,pc),a0	; routine
	move.l	a5,a1			; data
	bsr	forpath			; do it!

	tst.l	(a5)			; got seglist?
	bne.b	.exit			; yep, quit

	; could not find command, set ioerr accordingly
	move.l	#ERROR_OBJECT_NOT_FOUND,d1
	call	SetIoErr

.exit
	move.l	(d_fib,a5),d2		; get fib
	beq.b	.nofib
	moveq	#DOS_FIB,d1
	call	FreeDosObject
.nofib
	move.l	(a5),d0			; return success flag
	bne.b	.exitok

	; call failed, make sure fcmd structure is zeroed:
	clr.l	(fcmd_homedir,a5)
	clr.l	(fcmd_subqpos,a5)
.exitok
	movem.l	(sp)+,d2-d7/a0/a2-a6
	rts


.findseg	move.l	(fcmd_nameptr,a5),d1
	moveq	#0,d2
	call	FindSegment
	tst.l	d0
	beq.b	.fs_no

	move.l	d0,a0
	addq.l	#seg_UC,a0

	cmp.l	#CMD_DISABLED,(a0)	; disabled internal command?
	beq.b	.fs_no			; yeah, don't use this!

	move.l	(seg_Seg-seg_UC,a0),(a5) ; get seglist
	neg.l	(a5)			; mark non-UnLoadSeg()

	; resident cmd has no home dir!
	clr.l	(fcmd_homedir,a5)

	tst.l	(a0)			; seg_UC negative?
	bmi.b	.fs_no			; yeah, don't increment
	addq.l	#1,(a0)			; increment usecount!
	move.l	a0,(fcmd_subqpos,a5)	; set seg_UC ptr
.fs_no	rts


	; this routine checks one path element for
	; command.
.findroutine	movem.l	d2-d4/a2,-(sp)
	; d0=lock, a1=data, a6=dosbase
	move.l	a1,a2

	; change currentdir
	move.l	d0,d1
	call	CurrentDir
	move.l	d0,d3

	; try to get a lock
	move.l	(d_nameptr,a2),d1
	moveq	#ACCESS_READ,d2
	call	Lock
	move.l	d0,d4
	beq.b	.fr_nolock

	; examine the lock
	move.l	d4,d1
	move.l	(d_fib,a2),d2
	call	Examine
	tst.l	d0
	beq.b	.fr_bad

	; must have e bit clear (read: set)
	move.l	(d_fib,a2),a0
	btst	#FIBB_EXECUTE,(fib_Protection+3,a0)
	bne.b	.fr_bad

	; try to load as executable
	move.l	(d_nameptr,a2),d1
	call	LoadSeg
	move.l	d0,(a2)			; d_seglistptr
.fr_bad
	move.l	d4,d1
	call	UnLock
.fr_nolock
	; change back to old dir
	move.l	d3,d1
	call	CurrentDir
	move.l	d0,d1			; d1=path lock

	; found it? (d_seglistptr)
	moveq	#1,d0			; default: continue
	tst.l	(a2)
	beq.b	.fr_exit		; nope!

	; d1=lock
	call	DupLock
	move.l	d0,(d_homedir,a2)	; return homedir!
	moveq	#0,d0			; terminate
.fr_exit
	movem.l	(sp)+,d2-d4/a2
	rts


; forpath - call subroutine for each path element of process,
; if there is no CLI structure for process uses standard paths
; currentdir and c:.
;
;  IN: a0=function to call
;      a1=args to function
;      a6=dosbase
; OUT: d0=number of path elements encounted

; function is called with:
;  IN: a0=pointer to self
;      a1=args to function
;      a6=dosbase
;      d0=lock of this path
; OUT: d0=zero will break out from loop
;
forpath	movem.l	d1-d7/a0-a6,-(sp)
	moveq	#0,d7

	moveq	#0,d1
	call	CurrentDir
	move.l	d0,d2
	move.l	d0,d1
	call	CurrentDir

	move.l	d2,d0
	movem.l	(7*4,sp),a0-a1
	movem.l	d7/a6,-(sp)
	jsr	(a0)
	movem.l	(sp)+,d7/a6
	tst.l	d0
	beq.b	.exit

	addq.l	#1,d7

	call	Cli
	tst.l	d0
	beq.b	.nocli

	move.l	d0,a2
	move.l	(cli_CommandDir,a2),d0
	beq.b	.nocli

	lsl.l	#2,d0
	move.l	d0,a2

	movem.l	(7*4,sp),a0-a1

.more	move.l	(4,a2),d0		;lock
	movem.l	d7/a0-a2/a6,-(sp)
	jsr	(a0)
	movem.l	(sp)+,d7/a0-a2/a6
	tst.l	d0
	beq.b	.exit

	addq.l	#1,d7

	move.l	(a2),d0			;next
	lsl.l	#2,d0
	move.l	d0,a2
	bne.b	.more

.nocli	lea	(.stdpath,pc),a0
	move.l	a0,d1
	moveq	#ACCESS_READ,d2
	call	Lock
	move.l	d0,d6
	beq.b	.exit

	movem.l	(7*4,sp),a0-a1
	movem.l	d6-d7/a6,-(sp)
	jsr	(a0)
	movem.l	(sp)+,d6-d7/a6
	tst.l	d0
	beq.b	.nc_exit

	addq.l	#1,d7

.nc_exit	move.l	d6,d1
	call	UnLock

.exit	move.l	d7,d0
	movem.l	(sp)+,d1-d7/a0-a6
	rts

.stdpath	dc.b	'c:',0
	CNOP	0,2


	dc.b	'$VER: run 44.1 (27.1.00)',0

dosname	dc.b	'dos.library',0
nullstr	EQU	*-1
template	dc.b	'DETACH/S,QUIET/S,STACK/K/N,PRI/K/N,COMMAND/F',0
