;
; UnixDirs-Handler.asm   (c) 1989  Software Dimensions.
;          ______
;         /     /\        All Rights Reserved
;        /_____/ |\       Written by Murray Bennett and Mark Cyster
;       |\    |\  /|      Software Dimensions
;       | \/__|_\/ |      6th Floor, 239 Adelaide Tce
;       |__|     | |      Perth WA 6000
;        \ |  4  | /      Australia
;         \|_____|/
;                         BBS:(In Australia !??)
;
;----------------------------------------------------------------------------
; This material is Copyright (C) 1989 and 1990 Software Dimensions.
; It may be distributed freely as long as the following restrictions are met:
;
;    1.  All files present in the distribution package must be redistributed
;        with the package.  If you distribute on diskette, all files must be
;        on a single diskette.
;        Files in this distribution are listed in FILES.LIST
;
;    2.  The distributor agrees to cease distributing the programs and data
;        involved if requested to do so by authors or any member of The
;        Software Distillery.
;
; DISCLAIMER
;
; Neither Software Dimensions nor any of its members will be liable for
; any damage arising from the failure of this program to perform as described,
; or any destruction of other programs or data residing on a system
; attempting to run the program.  While we know of no damaging errors, the
; user of this program uses it at his or her own risk.
;
; DONATIONS
;
; Welcome (but we realise it's a long way to Australia).
;
;----------------------------------------------------------------------------
;
; Date 27-2-89
;
; This is the handler part of UnixDirs
;
; It is loaded into memory and the passed data is written here.
; The DOS library jump vectors are modified to point to these routines
; which check for directory/file names with . and .. imbedded
; Valid paths are replaced with the full path expansion and then the
; original DOS function is called. The return value is saved, the memory
; allocated for the new path name is freed, and then a return is made to
; the caller with the return code. If any error occurs during translation,
; a NULL is returned immediately.
;
; Assemble with the MANX assembler (3.6a) : as -N UnixDirs-Handler.asm
;
; Note that it is important to use the "no optimisation" flag (-N)
; To ensure that JumpTable is generated as full long jumps.
; The code has been hand optimised anyway.
;
; See the loader in UnixDirs.asm
;
; This program requires ARP.LIBRARY version 34 or later but
; has only been tested under version 34.1
;
;---------------------------------------------------------------------------
            NOLIST
            INCLUDE     "UnixDirs.i"
            LIST

            xref        _LVOAlert
            xref        _LVOAllocMem
            xref        _LVOCloseLibrary
            xref        _LVOCreateDir
            xref        _LVOCurrentDir
            xref        _LVODeleteFile
            xref        _LVODupLock
            xref        _LVOForbid
            xref        _LVOFreeMem
            xref        _LVOLoadSeg
            xref        _LVOLock
            xref        _LVOOpen
            xref        _LVOOpenLibrary
            xref        _LVOOutput
            xref        _LVOParentDir
            xref        _LVOPermit
            xref        _LVORename
            xref        _LVOSetComment
            xref        _LVOSetFunction
            xref        _LVOSetProtection
            xref        _LVOUnLoadSeg
            xref        _LVOUnLock
            xref        _LVOWrite

            FAR         CODE
            FAR         DATA

TableBase   equr        a0
ArpBase     equr        a4

            ; if they run the handler, just return
EntryPoint
            moveq       #0,d0
            rts

PassedData
            ds.b        UnixTable_Sizeof  ; this data is written by UnixDirs
JumpTable
            jmp         NewCreateDir
            jmp         NewDeleteFile
            jmp         NewLoadSeg
            jmp         NewLock
            jmp         NewOpen
            jmp         NewRename
            jmp         NewSetComment
            jmp         NewSetProtection

TP_BAD_PATH_SPEC        equ         0     ; Return codes from TranslatePath()
TP_NO_TRANSLATE         equ         1
TP_TRANSLATE            equ         2

NewFunction MACRO
            lea.l       PassedData,TableBase
            move.l      \1Num(TableBase),d0
            bra         TranslateAndCall
            ENDM

NewCreateDir
            NewFunction CreateDir

NewDeleteFile
            NewFunction DeleteFile

NewLoadSeg
            NewFunction LoadSeg

NewLock
            NewFunction Lock

NewOpen
            NewFunction Open

; Rename is special since it has two arguments which must be translated

NewRename
            lea.l       PassedData,TableBase
            move.l      RenameNum(TableBase),d0
            bsr         TranslatePath           ;translate 'from'name
            beq         BadName                 ; bad
            move.l      a0,-(sp)                ; save return code 1

            exg.l       d1,d2                   ; swap 'from' and 'to'
            bsr         TranslatePath           ; translate 'to' name
            beq         RenameBadName2          ; bad
            move.l      a0,-(sp)                ; save return code 2

            exg.l       d1,d2                   ; swap them back
            bsr         CommonDosRoutine        ; call the Rename routine

            move.l      (sp)+,a0                ; restore return code 2
            cmp.l       #TP_NO_TRANSLATE,a0
            beq         RenameNoFree2           ; No need to free path 2
            move.l      d2,a1
            bsr         FreePath                ; Free path 2

RenameNoFree2
            move.l      (sp)+,a0                ; restore return code 1
            cmp.l       #TP_NO_TRANSLATE,a0
            beq         RenameNoFree1           ; No need to free path 1
            move.l      d1,a1
            bra         FreePath                ; Free path 1

RenameNoFree1
            rts                                 ; all done

RenameBadName2
            moveq.l     #0,d0
            bra         RenameNoFree2

NewSetComment
            NewFunction SetComment

NewSetProtection
            NewFunction SetProtection


; Translates path name and calls orignal DOS function

TranslateAndCall
            bsr         TranslatePath
            beq         BadName
            move.l      a0,-(sp)                ; save return code
            bsr         CommonDosRoutine
            move.l      (sp)+,a0
            cmp.l       #TP_NO_TRANSLATE,a0
            beq         TAC_Return              ; No need to free path

            move.l      d1,a1
            bra         FreePath                ; free path and return

BadName     moveq.l     #0,d0
TAC_Return
            rts

; jumps to the common DOS routine
; the function is specified by d0
;
CommonDosRoutine
            movem.l     saveregs,-(sp)
            lea.l       PassedData,TableBase
            move.l      DOSFunction(TableBase),a1
            move.l      SaveArpBase(TableBase),a6    ; set up DosBase
            move.l      DosBase(a6),a6               ; in a6 in case
                                                     ; someone needs it
            jsr         (a1)
            movem.l     (sp)+,saveregs
            rts

saveregs    reg         d1-d7/a2-a6

;*********************************************************************
; NewPath = TranslatePath(OldPath)
;  d1  (a0)                 d1
;
; Translates the old path to a new path, taking into account . and ..
; a1 contains TP_BAD_PATH_SPEC on error
;             TP_NO_TRANSLATE  if no translation was required
;             TP_TRANSLATE     if translation was required
;
; For TP_TRANSLATE FreePath should be called to FreeMem the allocated path
;
; only modifies d1,a0. all other registers are preserved (including d0).
; z flag is set on error

OldPath                 equr        a2
NewPath                 equr        a3
CurrentLock             equr        d3
OldCurrentDir           equr        d4

TP_NO_ERROR             equ         0
TP_END_OF_PATH          equ         1
TP_TACK_ON              equ         2
TP_ERROR                equ         3

MAX_PATH_SIZE           equ         256
MAX_NUM_DIRS            equ         8

saveregs2               reg         d0/d2-d7/a1-a6

TranslatePath
            movem.l     saveregs2,-(sp)         ; save working registers

            move.l      d1,OldPath              ; remember old path
            lea.l       PassedData,TableBase
            move.l      SaveArpBase(TableBase),ArpBase ; get ArpBase

            ; Allocate space for new path

; Allocate space for new path name (Also used for temporary workspace)
TP_AllocateSpace
            move.l      AbsExecBase,a6
            move.l      #MAX_PATH_SIZE,d0
            moveq       #0,d1
            LINKEXEC    AllocMem
            tst.l       d0
            beq         TP_NoMemory
            move.l      d0,NewPath              ; remember new path

            move.l      DosBase(ArpBase),a6     ; a6 holds DosBase for
                                                ; most of this routine

            ; The first thing to do is to check to see if
            ; OldPath contains any . or .. directory names
            ; If it doesn't then a copy of the original path
            ; may be used. This cuts down on a great deal of
            ; overhead.

TP_ScanOldPath
            move.l      #TP_NO_ERROR,d5         ; used to break loop
            move.l      OldPath,a0
            move.l      NewPath,a1
            move.l      NewPath,a5              ; points to start of this
                                                ; dir name
TP_ScanNextChar
            move.b      (a0)+,d0
            move.b      d0,(a1)+
            beq         TP_EndScan
            cmp.b       #':',d0
            beq         TP_ScanSeparator
            cmp.b       #'/',d0
            bne         TP_ScanNextChar

            ; we have ':' or '/'
            ; null terminate it and compare against . and ..

TP_ScanSeparator
            move.b      d0,d1       ; separator
            subq.l      #1,a1
            move.b      #0,(a1)     ; Null terminate it
            move.b      (a5)+,d0
            cmp.b       #'.',d0
            bne         TP_ScanNormalDir        ; not . or ..
            move.b      (a5)+,d0
            beq         TP_ScanNeedTranslate    ; got .
            cmp.b       #'.',d0
            bne         TP_ScanNormalDir        ; not ..
            move.b      (a5)+,d0
            beq         TP_ScanNeedTranslate    ; got ..

            ; this is a normal dir (not . or ..) so replace the separator
            ; character and keep going if it isn't the last dir component

TP_ScanNormalDir
            move.b      d1,(a1)+                ; replace separator
            tst.l       d5
            bne         TP_NoNeedToTranslate    ; no . or .. found anywhere
            move.l      a1,a5                   ; start of new dir
            bra         TP_ScanNextChar

            ; This component is the last one
            ; set the flag to say so and then check for . or ..
TP_EndScan
            move.l      #TP_END_OF_PATH,d5
            bra         TP_ScanSeparator

            ; found . or .. somewhere along the way so we need to
            ; translate OldPath

TP_ScanNeedTranslate

            ; search for ':'

TP_FindVolume
            move.l      OldPath,a0
            move.b      #':',d0
            bsr         strchr                  ; a1 = strchr(OldPath,':')
            move.l      a1,d0                   ; test for == 0
            beq         TP_NoColon

            ; colon found, so get a lock on 'VOL:'

            ; copy 'VOL:' characters to temporary buffer (NewPath)
            ; Also increment OldPath to point to the character after the ':'

TP_CopyVolume
            move.l      NewPath,a0
.100        move.b      (OldPath)+,d0
            move.b      d0,(a0)+
            cmp.b       #':',d0
            bne         .100
            move.b      #0,(a0)                 ; Null terminate it

            ; check for '/' in 'VOL:'

TP_CheckSlashInVol
            move.l      NewPath,a0
            move.b      #'/',d0
            bsr         strchr
            move.l      a1,d0                   ; test for == 0
            bne         TP_SlashInVolume

TP_LockVolume
            move.l      NewPath,d1
            move.l      #ACCESS_READ,d2
            bsr         OldLock                 ; use old lock function
            tst.l       d0                      ; did it work?
            beq         TP_BadVolume

            ; Change directory to 'VOL:'

TP_CDVolume
            move.l      d0,CurrentLock          ; save current position
            move.l      d0,d1
            LINKDOS     CurrentDir              ; DosBase is already in a6
            bra         TP_DescendDirs          ; Now do the rest of the path

            ; No colon was found, so we use the current directory as
            ; the start of our descent
TP_NoColon
            move.l      AbsExecBase,a6
            move.l      ThisTask(a6),a6
            move.l      pr_CurrentDir(a6),d1    ; d1 = current directory
            move.l      d1,CurrentLock          ; save current position
            move.l      DosBase(ArpBase),a6
            LINKDOS     DupLock                 ; duplicate this lock
                                                ; for ease of processing

            ; Now we have :
            ;               d0 = Old current directory
            ;               OldPath = pointer to path to be descended
            ;                         below the top directory
            ;               NewPath = Temporary work space + space for
            ;                         final path
            ;               CurrentLock = lock on current directory

TP_DescendDirs
            move.l      #TP_NO_ERROR,d5         ; used to break loop
            move.l      d0,OldCurrentDir        ; must change back to
                                                ; this dir afterwards
TP_NextDir
            ; get directory name

            move.l      OldPath,a0
            move.l      NewPath,a1
.101        move.b      (a0)+,d0
            move.b      d0,(a1)+                ; Put it in NewPath as we go
            beq         TP_EndOfOldPath
            cmp.b       #':',d0
            beq         TP_InvalidDirectory     ; can't have ':' in dir name
            cmp.b       #'/',d0
            bne         .101

            ; Got directory name terminated with '/'

            subq.l      #1,a1
            move.b      #0,(a1)                 ; Null terminate it
TP_DescendThisDir
            move.l      OldPath,a5              ; Remember OldPath
            move.l      a0,OldPath              ; OldPath points to char
                                                ; after '/'
            move.l      NewPath,a0

            move.b      (a0)+,d0
            beq         TP_EmptyName            ; dir is ""
            cmp.b       #'.',d0
            bne         TP_NormalDirName        ; doesn't start in '.'

            move.b      (a0)+,d0
            beq         TP_EmptyName            ; dir is "." (do 'nothing')
            cmp.b       #'.',d0
            bne         TP_NormalDirName        ; doesn't start in '..'

            move.b      (a0),d0
            bne         TP_NormalDirName        ; isn't '..'

            ; got '..'

TP_ParentDir
            move.l      CurrentLock,d1
                                                ; DosBase is already in a6
            LINKDOS     ParentDir               ; get parent
            bra         TP_TestLockedDir        ; go and see if it worked

            ; This is a normal directory name (in NewPath),
            ; so attempt to get a lock on it
TP_NormalDirName
            move.l      NewPath,d1
            move.l      #ACCESS_READ,d2
            bsr         OldLock                 ; use old lock function
            tst.l       d0
            beq         TP_BadDirLock           ; this directory/file doesn't
                                                ; exist, so tack the last
                                                ; part of OldPath onto what
                                                ; we have so far and return
                                                ; that

            ; Test to see if the above ParentDir worked
            ;
TP_TestLockedDir
            tst.l       d0                      ; did it work?
            beq         TP_InvalidDirectory     ; no
            move.l      CurrentLock,-(sp)       ; save current dir
            move.l      d0,CurrentLock          ; new current dir
            move.l      d0,d1                   ; change dir
            LINKDOS     CurrentDir
            move.l      (sp)+,d1                ; unlock previous dir
            LINKDOS     UnLock

TP_EmptyName
            tst.l       d5                      ; are there more dirs?
            beq         TP_NextDir              ; yes

            ; Now we change back to the original directory
TP_DoneDirLoop
            move.l      OldCurrentDir,d1
                                                ; DosBase is already in a6
            LINKDOS     CurrentDir              ; the lock we are after is
                                                ; now in d0
                                                ; (as well as CurrentLock)
            cmp.l       #TP_ERROR,d5
            bne         TP_WritePathName        ; No error

            ; some sort of error occured, so free this lock and return BAD

            move.l      d0,d1
                                                ; DosBase is already in a6
            LINKDOS     UnLock
            bra         TP_BadDirSpecification

            ; all OK, so call PathName to write complete path name to NewPath

TP_WritePathName
            move.l      NewPath,a0
            move.l      #MAX_NUM_DIRS,d1
            move.l      ArpBase,a6
            LINKARP     PathName

            move.l      CurrentLock,d1          ; Unlock directory
            move.l      DosBase(ArpBase),a6
            LINKDOS     UnLock

            cmp.l       #TP_TACK_ON,d5          ; do we have to tack on the
                                                ; last bit of OldPath?
            bne         TP_NoTackOn             ; No

            move.l      NewPath,a0
            move.l      OldPath,a1
            move.l      ArpBase,a6
            LINKARP     TackOn

TP_NoTackOn
            move.l      NewPath,d5              ; all done
            move.l      #TP_TRANSLATE,a0
            bra.l       TP_Done

TP_EndOfOldPath
            move.l      #TP_END_OF_PATH,d5
            bra         TP_DescendThisDir

TP_InvalidDirectory
            move.l      #TP_ERROR,d5
            bra         TP_DoneDirLoop

TP_BadDirLock
            move.l      #TP_TACK_ON,d5
            move.l      a5,OldPath              ; restore pointer to last
                                                ; bit of OldPath
            bra         TP_DoneDirLoop

TP_NoNeedToTranslate
            move.l      #TP_NO_TRANSLATE,a0
            move.l      OldPath,d5

TP_FreeAndFinish
            move.l      NewPath,a1
            bsr         FreePath

TP_Done
            move.l      d5,d1                   ; return path in d1
                                                ; a0 must contain return code
            cmp.l       #TP_BAD_PATH_SPEC,a0    ; set zero flag on error
            movem.l     (sp)+,saveregs2         ; restore working registers
            rts

; Possible error conditions in Translate Path

TP_NoMemory
            moveq       #0,d5
            move.l      #TP_BAD_PATH_SPEC,a0
            bra         TP_Done

TP_SlashInVolume
TP_BadVolume
TP_BadDirSpecification
            moveq       #0,d5
            move.l      #TP_BAD_PATH_SPEC,a0
            bra         TP_FreeAndFinish

;*********************************************************************
; FreePath(buffer)
;           a1
;
; FreeMem's the buffer pointed to by a1.
; size is MAX_PATH_SIZE
; preserves d0,d1,a0
;
FreePath

            movem.l     d0/d1/a0,-(sp)
            move.l      AbsExecBase,a6
            move.l      #MAX_PATH_SIZE,d0
            LINKEXEC    FreeMem
            movem.l     (sp)+,d0/d1/a0
            rts

;*********************************************************************
; lock = OldLock(name,mode)
;  d0             d1   d2
;
; calls the old lock function
;
; NOTE : for now, calls the real lock function
;
OldLock
            lea.l       PassedData,TableBase
            move.l      LockNum(TableBase),d0
            bra         CommonDosRoutine


;*********************************************************************
; pt = strchr(string,char)
; a1            a0   d0.b
;
; finds first occurence of 'char'in 'string'
; returns pointer to 'char' if found or 0 if not found
; 'string' is null terminated
;
; if found, a1 points to 'char', a0 points to (a1 + 1)
;
; d1 is modified
;
strchr
.1
            move.b      (a0)+,d1    ; next byte
            cmp.b       d0,d1       ; check for = char
            beq         .2          ; yes, so point to it and exit
            tst.b       d1          ; check for end of string
            bne         .1          ; no, so go on to next character

            move.l      #0,a1       ; reached end of string with no match
            rts
.2
            move.l      a0,a1       ; found, so set up a1 and return
            sub.l       #1,a1
            rts
            end

