        SUBT    > Sources.TopPath
 [ Version >= 170
; The entire contents of this file were introduced in version 1.70

; These routines are the top level routines for manipulating file paths.
; They provide packaged complex path manipulations with checks for the
; kind of uses paths have:
;
; * To construct a path for OPENs for fsinfo_alwaysopen filing systems
;
; * To access a file for reading only (OPENIN, *Load, OS_File read attributes etc)
;
; * To access an existing file for update (OPENUP, OS_File change attributes etc)
;
; * To access a file which may or may not exist for creation/OPENOUT (rename too).
;
; TopPath_DoBusinessToPath
;
; All-singing, all-dancing do everything to a path to end up at an object. This
; gets to MultiFS directories from the underneath - it isn't too useful for
; enumerating them.
;
; In
;       r0 = Place to hold the cleaned-up user's path
;       r1 = Path as supplied by the user
;       r2 = Path var
;       r3 = Default path
;       r4 = Place to hold the munged beyond recognition resultant path
;       r5 = Flags:
;               Bit     Meaning
;               0       Relativity indicator (0 for @, 1 for %)
;               1       If an FS is fsinfo_alwaysopen then return immediately.
;               2       Return error if path strings or vars used are multi-element or
;                       if the leaf name is wildcarded. Used for OPENOUT/*create/*save
;                       style operations.
;               3       If found a directory, then extend with '.!Run', and treat as does
;                       not exist if that doesn't exist.
;               4       Don't object to a nul filename even if the FS doesn't have
;                       fsinfo_nullnameok set.
;               5       Don't try to ensure the object exists except when necessary.
;                       Necessary is resolving wildcards / deciding which path element
;                       applies - that sort of thing.
;                       This means that the object's info will be invalid - only
;                       the path parts may be relied upon, and those are only reliable
;                       for reconstructing paths.
;               6       Generate WildCards error for wildcarded leaf in source filename
;               7       For EnsureThingIsDirectory. This causes entry into a partition
;                       if a partition itself is specified. DoBusinessToPath ignores
;                       this flag bit.
;       r6 = Place to put linked special field
;
; Out
;       Error: bad name; ambigous name for write operation; other
;       or: Cleaned up and munged path ready for use and
;       r0..r5 are object's info
;       r6 = special field^ or scb^ ready for use
;       fscb^ set up
;
; TopPath_DoBusinessToPathFSPrefix
;
; As TopPath_DoBusinessToPath, except:
; out
;       r7 = 0 if no FS prefix on found thing, <> 0 otherwise
;
TopPath_RelativeToLib   *       1:SHL:0
TopPath_AlwaysOpen      *       1:SHL:1
TopPath_NoMultiParts    *       1:SHL:2
TopPath_TryPlingRun     *       1:SHL:3
TopPath_DontMindNuls    *       1:SHL:4
TopPath_Canonicalise    *       1:SHL:5
TopPath_NoWildCards     *       1:SHL:6
TopPath_WantPartition   *       1:SHL:7

        ^       0, sp
FSPath_Context  #       4
Appendment      #       4       ; Path tail to append to prefix(es) from path variable(s)
TopPath_ContextSize #   0

TopPath_DoBusinessToPath ENTRY "r7"
        BL      TopPath_DoBusinessToPathFSPrefix
        EXIT

TopPath_DoBusinessToPathFSPrefix ENTRY "r0-r9",:INDEX:TopPath_ContextSize
 [ debugpath
        DREG    r6, "DoBusinessToPath r6 in is "
 ]
        MOV     r7, r4

        ; Copy path from user space to my space, cleaning it up as we go
        BL      TopPath_CopyCleanTransAndPoliceWithError
 [ debugpath
        BVC     %FT01
        DLINE   "Barf up in TopPath_CopyCleanTransAndPoliceWithError"
01
 ]
        EXIT    VS
        ; <Path>: processing:
        ; init_path
        LDR     r1, [r0]
        ADR     r0, FSPath_Context
        ANDS    r4, r5, #TopPath_NoMultiParts
        MOVNE   r4, #FSPathConst_NoMultiparts
        BL      FSPath_Init_VariableWithDefault
 [ debugpath
        BVC     %FT01
        DLINE   "Barf up in FSPath_Init_VariableWithDefault"
01
 ]
        BVS     %FT95

        BL      strlen
        ADD     r8, r3, #1

        MOV     r9, #0                  ; Area size for munged path
        STR     r9, [r6]                ; Special field
        STR     r9, [r7]                ; Munged path
        STR     r1, Appendment          ; tail to attach to prefix

 [ debugpath
 DSTRING r1,"Path tail is "
 ]

10
        ; Main loop enumerating all the path elements

        ; Cancel any error - start with a clean slate
        MOV     r14, #0
        STR     r14, globalerror

        ; Restore r6 from passed in parameters
        LDR     r6, [sp, #Proc_LocalStack + 6*4]

        ; At the loop start the registers have the following allocation:
        ; r6 = Supplied special field ^^
        ; r7 = Address of pointer to mungable path
        ; r8 = Length of appendment + 1
        ; r9 = non-0 to indicate path allocated
        ; r10 = fscb
        ; r11 = fp
        ; r12 = wp
        ; r13 = sp
        ;
        ;    Construct <path bit><unpath bit>
        MOV     r3, r8
        ADR     r0, FSPath_Context
        BL      FSPath_LengthOfNextPrefix

        ; Get a new munge path area big enough for the path and the rest
        MOV     r0, r7
        TEQ     r9, #0
        MOV     r9, #0          ; To stop these failures repeating
        BLNE    SFreeLinkedArea
        BVS     %FT80
        BL      SGetLinkedArea
        MOV     r1, r2
        BVS     %FT80
        MOV     r9, r3          ; To indicate path allocated

15
        ; r1 = area to put new path into, so let's do it
        ADR     r0, FSPath_Context
 [ debugheapK
        CheckLinkedHeapBlock r1,A
 ]
        BL      FSPath_CopyNextPrefix

        LDR     r2, Appendment

        ; Remove . at end of paths when attachment is ""
        LDR     r14, [r7]
 [ debugheapK
        CheckLinkedHeapBlock r14,a
 ]
        TEQ     r1, r14
        BEQ     %FT16
        LDRB    r14, [r2]
        TEQ     r14, #0
        LDREQB  r14, [r1, #-1]
        TEQEQ   r14, #"."
        SUBEQ   r1, r1, #1
16
        BL      strcpy

        ; if construction fails policing then step the path
        LDR     r1, [r7]
 [ debugpath :LOR: debugheapK
 DSTRING r1,"Constructed path is "
 ]
 [ debugheapK
        CheckLinkedHeapBlock r1,B
 ]

        ; Police the name
        BL      PoliceFullName          ; (r1->V)
 [ debugpath
 BVS %FT01
 DLINE "Constructed path policed OK"
 B %FT02
01
 DLINE "Constructed path policed BAD"
02
 ]
        BVC     %FT17

        ; Only swallow error if Multi-part paths are allowed
        ; The error swallowing is to translate the error to 'Not found'
        LDR     r5, [sp, #Proc_LocalStack + 5*4]
        TST     r5, #TopPath_NoMultiParts
        MOVEQ   lr, #0
        STREQ   lr, globalerror

        B       %FT70

17

        ; Strip off FS prefix, and get the FS and special field
        BL      FSPath_FindFSPrefix     ; (r1)->(r1,r2,r3)
        MOV     r0, #1                  ; its a path which has failed
        BL      FSPath_ExtractFS        ; (r2,r3)->(r2,r3,r4,fscb)
        BVC     %FT20

 [ debugpath
        DLINE   "Error from ExtractFS"
 ]
        ; translate UnknownFilingSystem to a simple 'it wasn't found'
        LDR     lr, globalerror
        LDR     lr, [lr]
        TEQ     lr, #ErrorNumber_UnknownFilingSystem
        BEQ     %FT70
        SETV
        B       %FT80                   ; Bad FS prefix
20
        STR     r2, [sp, #Proc_LocalStack + 7*4]        ; r7 out
        TEQ     fscb, #Nowt
        BEQ     %FT70                   ; no FS applicable to the path

        ; If FS hates nul names make the error and treat as a 'not found' for now
        LDR     lr, [fscb, #fscb_info]
        TST     lr, #fsinfo_nullnameok
        LDREQ   r5, [sp, #Proc_LocalStack + 5*4]
        TSTEQ   r5, #TopPath_DontMindNuls
        BNE     %FT25

        ; Complain if resultant tail is nul
        LDRB    lr, [r1]
        CMP     lr, #delete
        CMPNE   lr, #space
        BLLS    SetMagicFSNeedsFilename
        BVS     %FT70

25
        ; If we're being sensitive to fsinfo_alwaysopen FSs, then skip the boring checking whether it exists!
        TST     r5, #TopPath_AlwaysOpen
        LDRNE   r5, [fscb, #fscb_info]
        TSTNE   r5, #fsinfo_alwaysopen
        MOVNE   r0, #object_file        ; fake up a file for alwaysopen FSs
        LDRNE   r1, [r7]
        LDRNE   r6, [r6]
        BNE     %FT60

        ; Take a copy of the special field
        TEQ     r3, #0
        LDRNEB  r5, [r3, r4]
        MOVNE   r14, #0
        STRNEB  r14, [r3, r4]
        MOV     r0, r6
        Push    "r1"
        MOV     r1, r3
        BL      SNewLinkedString
        Pull    "r1"
        TEQ     r3, #0
        STRNEB  r5, [r3, r4]
        BVS     %FT80

 [ debugpath
 LDR r0, [r6]
 DSTRING r0, "Special field is "
 ]

        ; Copy the rest down over the fs and special
        MOV     r2, r1
        LDR     r1, [r7]
        BL      strcpy
 [ debugheapK
        CheckLinkedHeapBlock r1,D
 ]

 [ debugpath
 DSTRING r1, "Leaving "
 ]

        ; Canonicalise construction, ensuring it exists
        MOV     r1, r7
        LDR     r3, [sp, #Proc_LocalStack + 5*4]
        TST     r3, #TopPath_Canonicalise
        AND     r3, r3, #TopPath_RelativeToLib
        BEQ     %FT40

        ; Canonicalise only - check path context for applicability of laziness
        ADR     r0, FSPath_Context
        BL      FSPath_AllDone
        BNE     %FT40

        ; It's the last element of a path and only need to canonicalise it so do that
        BL      CanonicalisePath
        LDRVC   r1, [r1]
        LDRVC   r6, [r6]
        B       %FT60

40
        BL      EnsureObjectCommon      ; (r1,r6,fscb)->(r0-r6,fscb)
 [ debugpath
        BVC     %FT01
        DLINE   "Error from EnsureObjectCommon"
01
 ]
        BVS     %FT70                   ; Simply go to the next path element if error

        ; If it exists return results (full info on object - for free!)
        TST     r0, #object_file :OR: object_directory
        BEQ     %FT70

        ; Check for <dir>.!Run sensitivity
        TST     r0, #object_directory
        BEQ     %FT60
        LDR     r14, [sp, #Proc_LocalStack + 5*4]
        TST     r14, #TopPath_TryPlingRun
        BEQ     %FT60

        ; Sensitive to <dir>.!Run - attach a .!Run and try again

        ; Evaluate the length of the .!Run string
        LDR     r4, [r7]        ; old string for disposal later
        SUB     r5, r1, r4      ; remember how far through we are
        MOV     r1, r4
 [ debugheapK
        CheckLinkedHeapBlock r1,E
 ]
        BL      strlen
        ADD     r3, r3, #?DotPlingRun + 1


        ; Get a new block for the string - if error restore the old block and leave
        MOV     r0, r7
        BL      SGetLinkedArea
        STRVS   r4, [r7]
        BVS     %FT80

        STR     r2, [r7]

        ; Construct the string: <old tail>.!Run
        MOV     r1, r2
        MOV     r2, r4
        BL      strcpy_advance
        addr    r2, DotPlingRun
        BL      strcpy_advance

        ; Junk the old block
        Push    r4
        MOV     r0, sp
        BL      SFreeLinkedArea
        ADD     sp, sp, #4
        BVS     %FT80

        ; Find out what we've got
        LDR     r1, [r7]
        ADD     r1, r1, r5      ; get back to where we were before
 [ debugheapK
        CheckLinkedHeapBlock r1,F
 ]
        BL      EnsureCanonicalObject
        BVS     %FT70                   ; Once again go around for another go if bad

        ; Reject anything but pure files
        TEQ     r0, #object_file
        BNE     %FT70

60
        ; Save the results
        ADD     r14, sp, #Proc_LocalStack
        STMIA   r14, {r0,r1,r2,r3,r4,r5,r6}

        ; Junk the pathenum object now we've finished with it
        ADR     r0, FSPath_Context
        BL      FSPath_DisposeContext
        BVS     %FT85
        EXIT
        
70
        ; Drop any error
        CLRV

        ; Step to the next path element
        ADR     r0, FSPath_Context
        BL      FSPath_StepContext
        BVS     %FT80

 [ debugpath
 DLINE "Stepped context"
 ]

        ; If there's more path have another go
        BNE     %BT10

 [ debugpath
 DLINE "Reached end"
 ]
        ; Upgrade last non-UnknownFilingSystem error to a real V-set error
        LDR     lr, globalerror
        TEQ     lr, #0
        SETV    NE
        BVS     %FT80

        ; Reached end of all paths so return didn't exist, and cancel any error
        MOV     r0, #object_nothing
        STR     r0, [sp, #Proc_LocalStack + 0*4]
        STR     r1, [sp, #Proc_LocalStack + 1*4]
        STR     r6, [sp, #Proc_LocalStack + 6*4]

 [ debugpath
 DLINE "Quitting"
 ]

        EXIT

        ; Tidy up from error inside the loop
80
 [ debugpath
        LDR     r0, globalerror
        DREG    r0, "globalerror is now(A):"
 ]

        ; Junk the pathenum object
        ADR     r0, FSPath_Context
        BL      FSPath_DisposeContext
 [ debugpath
        LDR     r0, globalerror
        DREG    r0, "globalerror is now(B):"
 ]

85
        ; Junk the mungeable path
        MOV     r0, r7
        BL      SFreeLinkedString
 [ debugpath
        LDR     r0, globalerror
        DREG    r0, "globalerror is now(C):"
 ]

90
        ; Junk the special field
        LDR     r0, [sp, #Proc_LocalStack + 6*4]
 [ debugpath
        LDR     lr, [r0]
        DREG    lr, "Freeing linked string "
 ]
        BL      SFreeLinkedString
 [ debugpath
        LDR     r0, globalerror
        DREG    r0, "globalerror is now(D):"
 ]

95
        MOV     r1, r0

        ; Junk the cleaned-up path
        LDR     r0, [sp, #Proc_LocalStack + 0*4]
        BL      SFreeLinkedString
 [ debugpath
        LDR     r0, globalerror
        DREG    r0, "globalerror is now(E):"
 ]

        STR     r1, [sp, #Proc_LocalStack + 0*4]
        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; TopPath_DoBusinessForDirectoryRead
;
; In
;       r1 = Path as supplied by the user
;       r2 = Path var
;       r3 = Default path
;       r5 = flags as for TopPath_DoBusinessToPath
;
; Out
;       Error: bad name; ambigous name for write operation; other
;       or: Cleaned up and munged path ready for use and
;       r1 = pointer to tail of path (normal path/nul string)
;       r6 = special field^ or scb^ ready for use
;       fscb^ set up
;
; Uses PassedFilename, FullFilename and SpecialField
;
TopPath_DoBusinessForDirectoryRead ENTRY "r0,r2,r3,r4,r5,r6"
        ADR     r0, PassedFilename

        LDRB    r14, [r1]
        CMP     r14, #delete
        CMPNE   r14, #space
        BHI     %FT10

        ; nul-filename passed in - default to relativity indicator
        ; This avoids the silly "Directory '' not found" error
        TST     r5, #1
        ADREQ   r1, %FT97
        ADRNE   r1, %FT98

10
        ADR     r4, FullFilename
        ORR     r5, r5, #TopPath_DontMindNuls
        ADR     r6, SpecialField
        BL      TopPath_DoBusinessToPath
        EXIT    VS

        Push    "r1,r2,r5"
        LDR     r1, PassedFilename
        LDR     r2, FullFilename
        LDR     r5, [sp, #3*4+4*4]              ; r5 In
        BL      TopPath_EnsureThingIsDirectory
        Pull    "r1,r2,r5"

        ; We've got to the right place if it's a pure directory
        BLVC    AssessDestinationForPathTailForDirRead ; only does things on partitions
        BVS     %FT95

        STR     r6, [sp, #5*4]
        EXIT

95
        BL      JunkFileStrings
        EXIT

97
        DCB     "@",0
98
        DCB     "%",0
        ALIGN

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; TopPath_EnsureThingIsDirectory
;
; In
;       r0 = object type (dir/dir+file)
;       r1 = pointer to user's path
;       r2 = pointer to full path
;       r5 = TopPath flags (TopPath_WantPartition relevant)
;       fscb (may be Nowt)
;
; Out
;       error if object isn't directory thing
;       Note that non-existant things on an openalways filing system will be
;       accepted as directories
;
TopPath_EnsureThingIsDirectory ENTRY   "r0,r1,r2"

        ; Error if does not exist
        CMP     r0, #object_nothing     ; VC
        BNE     %FT40

        TEQ     fscb, #Nowt
        LDRNE   lr, [fscb, #fscb_info]
        TSTNE   lr, #fsinfo_alwaysopen
        EXIT    NE

        TST     r5, #TopPath_WantPartition
        BLEQ    SetMagicDirNotFound
        BLVC    SetMagicPlainNotFound
        EXIT

40
        ; Error if it isn't a directory
        TST     r0, #object_directory
        EXIT    NE

        LDR     lr, [fscb, #fscb_info]
        TST     lr, #fsinfo_multifsextensions
        BEQ     %FT60

        ; It's a file - choose a suitable error depending on whether the path ended $ or not
        LDRB    r0, [r2]
50
        LDRB    lr, [r2], #1
        TEQ     lr, #0
        MOVNE   r0, lr
        BNE     %BT50

        TEQ     r0, #"$"

        addr    r0, ErrorBlock_NotGoodDisc, EQ
        BLEQ    CopyError
        EXIT    VS

        ; If didn't end $ and WantPartition then there's a root *directory* and
        ; so WantPartition is satisfied
        TST     r5, #TopPath_WantPartition
        EXIT    NE

60
        BLVC    SetMagicIsAFile
        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; TopPath_CopyCleanTransAndPoliceWithError
;
; In    r0 -> Place to put linked clean shiny path
;       r1 -> user's grotty path
;       r5 = flags
;            TopPath_NoWildCards actioned here
;
; Out   Error - user's path was real grubby!
;       or path has been copied, cleaned and policed
;
TopPath_CopyCleanTransAndPoliceWithError ENTRY "r0,r1,r2,r4,r5,r6"
        MOV     r6, r0

        GetLumpOfStack  r5, #16, #StaticName_length, #2048, %F90

        MOV     r0, r1
        MOV     r1, sp
        ORR     r2, r5, #2_011 :SHL: 29
        SWI     XOS_GSTrans
        BLVS    CopyErrorExternal
        BVS     %FT85

        BCS     %FT89

        ; Strip leading and trailing spaces
        ADD     r2, r1, r2              ; r2 -> past end of translated name
10      CMP     r2, r1                  ; Back to start ?
        BEQ     %FT20
        LDRB    r14, [r2, #-1]!         ; Loop back till not space
        CMP     r14, #space
        BEQ     %BT10
        MOV     r14, #0                 ; Terminate (r2 -> last char in string)
        STRB    r14, [r2, #1]

20      BL      FS_SkipSpaces           ; If we have leading spaces, copy
        MOV     r2, r1
        MOV     r1, sp
        BL      strcpy

        ; Path is now leading and trailing space stripped,
        ; translated and nul terminated.

        ; Check path doesn't have any spaces quotes or soliduses
        MOV     r1, sp
30
        LDRB    r14, [r1], #1
        TEQ     r14, #space
        TEQNE   r14, #quote
        TEQNE   r14, #solidus
        BEQ     %FT80
        TEQ     r14, #0
        BNE     %BT30

        ; Police the name normally, after having stripped any FS as necessary
        MOV     r1, sp
        BL      PoliceFullName
        BVS     %FT80

        ; Check for WildCards if necessary
        ADD     lr, sp, r5
        LDR     lr, [lr, #4*4]          ; r5 in
        TST     lr, #TopPath_NoWildCards
        BEQ     %FT70

        ; If WildCarded generate error
        MOV     r0, r1
        MOV     r2, #0
40
        LDRB    lr, [r0], #1
        TEQ     lr, #"#"                ; wildcards?
        TEQNE   lr, #"*"
        MOVEQ   r2, #1
        TEQ     lr, #":"                ; path break?
        TEQNE   lr, #"."
        MOVEQ   r2, #0
        CMP     lr, #" "                ; end of path?
        BHI     %BT40

        TEQ     r2, #1
        BNE     %FT70

        ADRL    r0, MagicErrorBlock_WildCards
        BL      MagicCopyError
        B       %FT85

70
        ; Get the copy to where it's wanted.
        MOV     r0, r6
        BL      SGetLinkedString
        ADD     sp, sp, r5
        EXIT

80
        MOV     r1, sp
        BL      SetMagicBadFileName
85
        ADD     sp, sp, r5
        EXIT
89
        ADD     sp, sp, r5
90
        ; Error exit without enough stack
        addr    r0, ErrorBlock_NotEnoughStackForFSEntry
        BL      CopyError
        EXIT

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; TopPath_DoBusinesToPathForRun
;
; In
;       r1 = Path as supplied by the user
;
; Out
;       Error: File not there; is a directory etc, or
;       or: Cleaned up and munged path ready for use and
;       r0..r5 are object's info
;       r6 = special field^ or scb^ ready for use
;       fscb^ set up
;       Places used for various parts are:
;       PassedFilename
;       FullFilename
;       SpecialField

TopPath_DoBusinessToPathForRun ENTRY "r0-r6"
        ADR     r0, PassedFilename
        addr    r2, RunPathVariableNameCR
        ADD     r3, r2, #(RunPathVariableDefault - RunPathVariableNameCR)
        ADR     r4, FullFilename
        MOV     r5, #TopPath_TryPlingRun
        ADR     r6, SpecialField
        BL      TopPath_DoBusinessToPath
        EXIT    VS

        ; Check what we got
        TEQ     r0, #object_nothing
        BEQ     %FT70
        TST     r0, #object_directory

        ; Return results if OK
        STMEQIA sp, {r0-r6}
        EXIT    EQ

65
        ; Error exit with '<blah> is a directory'
        LDR     r1, PassedFilename
        BL      SetMagicIsADirectory
        B       %FT75

70
        ; Error exit with 'file <blah> not found'
        LDR     r1, PassedFilename
        BL      SetMagicFileNotFound
75
        BL      JunkFileStrings
        EXIT
 ]

; ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
;
; PoliceFullName
;
; In    r1 = pointer to name to be policed
;
; Out   Name policed (VS if error)
;
PoliceFullName ENTRY "r1,r2,r3"

        BL      FSPath_FindFSPrefix
 [ debugpath
        DSTRING r1, "FS-prefix stripped path="
        DSTRING r2, "FS-prefix="
        DREG    r3, "with length"
 ]
        BL      PoliceNameWithError
        EXIT

        END
