* CD.Asm
*
*       Written 1/7/87 by C.Heath
*
*       Copyright (c) 1986 by C.Heath of Microsmiths Inc.
*
*       This program may be freely copied for individual use.  It may
* also be freely distributed with the following restrictions:
*
*       1) No modifications to the source code are made.  If an executable
* version of the program is distributed, use assem and blink 6.7, which
* should result in an executable of 600 bytes. 
*       2) Please verify that the most current version is distributed. 
* This can be verified by contacting "cheath" on BIX, or 74216,2117 on CIS, 
* or write to Microsmiths, Inc., PO Box 561, Cambridge, MA 02140. 
*       3) The ARP series may be distributed with commercial software 
* except as noted in (4) provided it's origin is stated.  Please contact 
* me to insure you have the latest version. 
*       4) This module may NOT be distributed with any commercial software 
* which encourages the BCPL program interface without express written 
* permission.  This restriction includes the Metacomco Shell.

*       This module replaces the ADOS "CD" command.  It matches the
* ADOS prompt syntax.
*       In addition, it allows a simple path substitution to be made.  This
* must work in tandem with the ARP "Prompt" command.
*       This command is smaller than the ADOS equivalent so runs a bit faster.

*       This is installment ~8 of the "ARP" series.  The're coming fast
* and furious now, I've lost count.

******  Equates and publics     ***************************************

        INCLUDE "exec/types.i"
        INCLUDE "libraries/dos.i"
        INCLUDE "libraries/dosextens.i"

MIN_LIB         set     $1f             ; Library minimum rev level!
                                        ; ADOS "stack" command

PLENGTH         set     60              ; Maximum length of prompt string

*       The CD hack works by substituting a flag value for the %p
* The flag used is a character value of $80.  This character will
* be IGNORED by the console handler.  It is a NOP.  Then, when the
* ARP version of CD is run, if it finds the flag character in the
* prompt it appends the new current dir text at that offset in the
* prompt string.
CD_FLAG         set     $80             ; Search for this flag value

*** WITH MANX, SUBSTITUTE public FOR xref
xlib    macro   
        xref    _LVO\1
        endm

*** BEGIN MANX code.  Uncomment the next few lines to assemble with MANX
*
*       entry   .begin
*       public  .begin
*.begin
*** END MANX CODE

        xlib    OpenLibrary
        xlib    CloseLibrary
        xlib    FindTask

        xlib    Output
        xlib    Write

        xlib    Lock
        xlib    Examine
        xlib    UnLock
        xlib    CurrentDir
        xlib    ParentDir

******  The Program             ***************************************

* Register use
*
* A6    Execbase, then DOSBase through the rest of execution
* A5    Pointer to a FIB, at times
* A4    Process structure
* A3    pr_CLIStruct
* A2    Command Line string, until after it is completely processed

* D7    Holds new lock for CurrentDir
* D6    Saved Stack Pointer for Exit()
* D5    Holds the "parent" for this directory
* D4    Holds count used while ascending directories in do_dir_text
* D3    Used as calling arg length for do_write
* D2    Used as calling arg string for do_write

Start:
        move.l  sp,D6                   ; Save stack pointer
        move.l  4,A6                    ; ExecBase in A6 until DOSBase later
        move.l  A0,A2                   ; Register A2 is cmdline henceforth

        suba.l  A1,A1
        jsr     _LVOFindTask(A6)
        move.l  D0,A4                   ; Process base in A4 henceforth
        move.l  pr_CLI(A4),D0
         beq.s  BadExit                 ; Not a CLI process. Punt
        asl.l   #2,D0
        move.l  D0,A3                   ; A3 points at CLIStruct henceforth

        lea     DOSName(pc),A1          ; Open DOS.library
        moveq.l #MIN_LIB,d0
        jsr     _LVOOpenLibrary(A6)
        move.l  D0,A6
        or.l    D0,D0
         bne.s  ok_start                ; Continue if library OK,

* fall in to BadExit code if DOS library couldn't open

************************************************************************
*
* BadExit is where to go if there is an error opening the DOS library
*       This should only happen if things are really sick
*
************************************************************************
BadExit:
                moveq   #20,D0
                rts

************************************************************************
*
* ok_start
*       Proceed with normal code, check the input string.
*
************************************************************************
ok_start:

*
* Process the input command string
*
skpspc: move.b  (A2)+,D1                ; Skip spaces 
        cmp.b   #$20,D1
         beq.s  skpspc
        cmp.b   #$0A,D1                 ; check for a NULL string ?
         beq.s  show_cd                 ; Ok, just show the current dir.

        cmp.b   #'"',D1
         beq.s  do_setdir               ; String in Quotes, D1 is EOS test
        
        subq    #1,A2                   ; Back up a char
        clr     D1                      ; Clear terminator test char
*
* Here, set the directory to the string at A2
*
do_setdir:
        move.l  A2,A0
        moveq   #0,D0
lla:    move.b  (A0)+,D0
         beq.s  lalala
        cmp.b   D1,D0
         beq.s  lalala                  ; End of quoted string
        cmp.b   #$0A,D0
         bne.s  lla

lalala:
        clr.b   -1(A0)                  ; Turn the terminater into a
                                        ; null terminating string
        move.l  A2,D1
        move.l  #ACCESS_READ,D2
        jsr     _LVOLock(A6)
        move.l  D0,D7
         beq.s  no_object               ; Can't find directory

        sub     #fib_SIZEOF,sp          ; Reserve space on stack for fib
        move.l  sp,D2
        move.l  D7,D1
        jsr     _LVOExamine(A6)
        tst.l   D0
         beq.s  not_dir                 ; Bad error, couldn't Examine()

        tst.l   fib_DirEntryType(sp)
         ble.s  not_dir                 ; It's a file, not a dir

* Note that the FIB won't be valid after ANY calls or pushes.
        add     #fib_SIZEOF,sp          ; Restore stack pointer

        move.l  D7,D1
        jsr     _LVOCurrentDir(A6)      ; Set new currentdir

        move.l  D0,D1
        jsr     _LVOUnLock(A6)          ; Free old currentdir Lock

        bsr     do_dir_text             ; Set the new text for currentdir
        bra.s   CleanUp


***************************************************************************
*
* show_cd
*       CD was called with no arguement.  Just display the current
* value of CurrentDir.
*
***************************************************************************
show_cd:
        bsr.s   do_dir_text     ; Update cli_SetName and prompt 

        move.l  cli_SetName(A3),D0
        asl.l   #2,D0
        move.l  D0,A0

        moveq   #0,D3
        move.b  (A0)+,D3        ; Get length of currentdir BSTR in A3
        move.l  A0,D2
        bsr.s   do_writex

        lea     LF_str(pc),A0
        bsr.s   do_write

        moveq   #0,D0
CleanUp:
        move.l  D0,D2                   ; Save return value
        move.l  A6,A1
        move.l  4,A6                    ; AbsExecBase
        jsr     _LVOCloseLibrary(A6)

        move.l  D6,SP                   ; Restore initial SP
        move.l  D2,D0                   ; Restore return value
        rts                             ; and exit()


***************************************************************************
*
* not_dir
*       Come here if the new requested CD is not a directory!
*
not_dir:
        add     #fib_SIZEOF,sp          ; Restore stack pointer
        move.l  D7,D1
        jsr     _LVOUnLock(A6)          ; Free the new lock.

        move.l  A2,A0
        bsr.s   do_write

        lea     str_notdir,A0           ; Print this message and exit(20)
        bra.s   err_mrg

***************************************************************************
*
* no_object
*       Error can't find the requested directory.
*
no_object:
        lea     str_noobj(pc),A0
        bsr.s   do_write

        move.l  A2,A0
        bsr.s   do_write

        lea     LF_str(pc),A0

***************************************************************************
*
* err_msg
*       Merge point for some error messages.  Exits through cleanup
*
***************************************************************************
err_mrg:
        bsr.s   do_write

        moveq   #20,D0                  ; Set to exit(20)
        bra.s   CleanUp


***************************************************************************
*
* do_write
*       Simple write string.
*
*       Assumes A6 = DOSBase
*       Null terminated string is @A0
*       Also assumes a reasonable CLI environment with valid COS
*
*       Zaps D0..D3/A0/A1
*
***************************************************************************
do_write:
        move.l  A0,D2
        moveq   #-1,D3
dwl1:   addq.l  #1,D3
        tst.b   (A0)+
         bne.s  dwl1

do_writex:                      ; BCPL style entry with D2/D3 preset...
        jsr     _LVOOutput(A6)
        move.l  D0,D1
        jmp     _LVOWrite(A6)


***************************************************************************
*
* do_dir_text
*       Set up text version of cli_SetName to reflect current directory
*
***************************************************************************
do_dir_text:
        sub     #fib_SIZEOF,sp          ; Reserve space on stack for fib
        move.l  sp,A5                   ; Set A5 pointing to fib

        moveq   #0,D4                   ; Set artificial limit of 20
                                        ; directories to avoid endless loop

        move.l  pr_CurrentDir(A4),D7    ; Get terminal directory lock

        move.l  cli_SetName(A3),D0
        asl.l   #2,D0
        move.l  D0,A2
        clr.b   (A2)                    ; Set string to "null"

        bsr.s   ascend

        add     #fib_SIZEOF,sp

* Here, fall into the code to set up prompt string, if %p is selected.

***************************************************************************
*
* DoKluge
*
*       Test for the special flag value for the Prompt command.
*
*       Here, concatenate the current pathname onto the prompt
* string and set the flag, offset bytes.
*
* NOTE: If the resulting string would be too long, restore the
* standard DOS prompt and report that was done.
*
***************************************************************************
DoKluge:
        move.l  cli_Prompt(A3),D0       ; Get BSTR pointing to prompt
        asl.l   #2,D0
        move.l  D0,A0                   ; A0 = where to store length
        lea     1(A0),A1                ; A1 = where to store string

        moveq   #0,D1                   ; D1 = offset @ start of string
        move.b  (A0),D0                 ; Search current prompt string
dkl1:   addq    #1,D1                   ; Increment offset
        cmp.b   #CD_FLAG,(A1)+          ; for CD_FLAG value
         beq.s  doklug
        subq.b  #1,D0                   ; Loop through all of string
         bgt.s  dkl1
        bra.s   NoKluge                 ; Nope, leave prompt alone!

doklug:
        move.b  D1,(A0)                 ; Reset length to start of %p

        move.l  cli_SetName(A3),D0
        asl.l   #2,D0
        move.l  D0,A2                   ; Get the BSTR for path name

        move.b  (A2),D2                 ; Length of path name
        move.b  D2,D0                   ; Save length of CD string in D2
        add.b   D1,D0                   ; combined length
        cmp.b   #PLENGTH,D0
         bge.s  NoKluge                 ; Safety punt.  Extra char for >

        lea     1(A2),A2
cl1:    move.b  (A2)+,(A1)+             ; Copy the string
        addq    #1,D1
        subq.b  #1,D2
         bgt.s  cl1

        move.b  #'>',(A1)               ; Append a > character
        addq    #1,D1                   ; Plus a tally ho
        move.b  D1,(A0)                 ; Save the length

NoKluge:
        rts     


***************************************************************************
*
* ascend
*       Recursive routine to create CurrentDir text.
*       Call with D7 = a valid child lock
*
ascend:
        cmp     #20,D4
         bge.s  punt                    ; Probable disk error looped

        move.l  D7,D1
        jsr     _LVOParentDir(A6)
        move.l  D0,D5
         beq.s  no_parent               ; D5 is a flag "no parent" if 0

        addq    #1,D4                   ; Bump tally of parents
        move.l  D7,-(sp)                ; Save the current directory
        move.l  D5,D7                   ; Now go fetch this one
        move.l  D5,-(sp)
        bsr.s   ascend                  ; Go up another level

        subq    #1,D4                   ; Reduce parents level

        move.l  (sp)+,D1                ; Restore parent dir into D1
        move.l  (sp)+,D7                ; Restore current direcory
        jsr     _LVOUnLock(A6)          ; Free the extra ParentDir Lock.

        moveq   #1,D5

* no_parent
*       Here get the text for this directory and append it.
no_parent:
        move.l  A5,D2
        move.l  D7,D1
        jsr     _LVOExamine(A6)
        tst.l   D0
         beq.s  punt                    ; Bad error, couldn't Examine()

        tst.l   fib_DirEntryType(A5)
         ble.s  punt                    ; Parent of a dir not a dir. Hmm...

        lea     fib_FileName(A5),A0     ; Append this dir string 
npl1:   move.b  (A0)+,D0
         beq.s  npl2
        bsr.s   do_putch
        bra.s   npl1

npl2:
        move.b  #':',D0
        tst     D5
         beq.s  do_putch                ; if ZERO, it's the base

        move.b  #'/',D0
        tst     D4
         bne.s  do_putch                ; if NZ, it's got a parent

npl3:   rts


***************************************************************************
*
* punt
*       Fatal error while ascending the directory tree.
*       This should never ever happen.  But it might...
*
punt:
        lea     str_baderr(pc),A0
        bra     err_mrg                 ; Merge at string error printer

***************************************************************************
*
* do_putch
*       Append the character in D0 to the SetName CD string
*       @(A2).  A2 is a BSTR with maximum length of xxx bytes
*       
do_putch:
        cmp.b   #70,(A2)
         bgt.s  1$                      ; CD string longer than storage
                                        ; Just truncate it.
        addq.b  #1,(A2)
        moveq   #0,D1
        move.b  (A2),D1
        move.b  D0,0(A2,D1)
1$:     rts

***************************************************************************
*
* Following is just constant data used by the program
*
***************************************************************************

DOSName:        dc.b    'dos.library',0

str_noobj:      dc.b    'Can''t find ',0

str_notdir:     dc.b    ' is a file not a directory'
LF_str:         dc.b    $0A,0

str_baderr:     dc.b    'Fatal error in CD',$0A,0

        END
