! Directory package for new filestores.  This version has a translation cache.
!
! Uses B-tree package for key manipulation, and related data-storage
! mechanism for keeping large equivalence records.
!
! Strategy is that if the translation is a single file ID then it
! is inserted directly into the tree.  Otherwise, if it has several
! versions, or is a textual equivalent, then it is packaged up in a
! separate record as follows:
!     Each record starts with a tag field, indicating whether the translation
!     is a list of IDs, a local translation, or an external translation.
!     If it is a list of IDs, then there is a count field, followed by the
!     corresponding IDs.  If it is a textual translation, then the actual
!     text appears as a string immediately after the tag field.
!
! Paths can be either absolute (relative to the root) or start with a
! file ID.  Paths are presented as a list of components (already split).
!
! IDs which do not explicitly specify a partition are taken to be in the
! same partition as their parent.  The partition ID is taken as 4 bits of
! partition-within-drive + 2 bits of drive.  This is a NASTY HACK -- it's
! necessary to allow entire drives to be moved to another system which might
! well result in partition number clashes otherwise.  If the drive-ID isn't
! specified it too defaults to the parent's drive.  This has the unfortunate
! side-effect that we can't refer to files on drive 0 from directories on
! other drives except via the root (redirector).  We'll worry about this at
! some other time, though -- just make everything drive-relative for now...
! BEWARE: the constants are hard-coded in!!
!
! The top few bits of IDs are reserved for use by the directory structure.
! <non ID> and <directory> are defined at present

%externalstring(47) copyright %alias "GDMR_(C)_DIR" = %c
   "Copyright (C) 1987, 1988 George D.M. Ross"

%option "-low-nonstandard-nocheck-nodiag-noline"
!%option "-low-nonstandard"

%constinteger NUL = 0

! +ve error codes indicate redirectors
%constinteger dud path              error = -201
%constinteger not a directory       error = -202
%constinteger dud component         error = -203
%constinteger non empty directory   error = -204
%constinteger no versions           error = -205
%constinteger version not found     error = -206
%constinteger too many versions     error = -207
%constinteger no privilege          error = -208
%constinteger dud redirect level    error = -209

%include "Moose:Mouse.Inc"

%include "GDMR_H:FSysAcc.Inc"
%include "GDMR_H:FSys.Inc"
%include "GDMR_H:Tree.Inc"

%externalroutinespec fsys initialise
%externalintegerfnspec fsys get full ID(%integer partial, %integername full)

%externalpredicatespec FS lookup(%string(31) what, %integername value)
%externalroutinespec FS insert(%string(31) what, %integer value)

%externalintegerfnspec global heap get(%integer amount)

%externalstring(127)%fnspec itos(%integer i, n)
%systemroutinespec phex(%integer x)

%constinteger infinity = 16_7FFFFFFF

%constinteger world read access = 16_20000000

%constinteger directory          flag = 16_40000000
%constinteger non ID             flag = 16_80000000
%constinteger directory relative flag = non ID flag

%recordformat path fm(%record(path fm)%name next,
                      %integer version, %string(*)%name key,
                      %string(255) text)

!constinteger partition shift = 24
!constinteger partition mask  = 16_3F000000
!constinteger ID        mask  = 16_00FFFFFF

%constinteger max compound ID = 32;  ! Meantime
%recordformat compound fm(%short tag,
                          ((%short count,
                            %integerarray ID(1 : max compound ID)) %c
                       %or %string(255) text))
%constinteger compound ID           tag = 1
! Unfortunately we used '1' to indicate a compound ID, so we have to
! use redirect-levels of 2 or more.....

%constinteger ID escape character = 2


! Translation cache

%constinteger cache slot entries = 100
%constinteger cache slots = 64

%constinteger cache name limit = 23
%recordformat cache entry fm(%string(23) name, %integer ID)
%recordformat cache slot fm(%integer n, stamp,
                            %record(cache entry fm)%array e(1 : cache slot entries))
%recordformat cache fm(%record(semaphore fm) semaphore,
                       %integer hits, misses, new dirs, new entries,
                       %integerarray ID(1 : cache slots),
                       %record(cache slot fm)%array s(1 : cache slots))

%owninteger cache stamp = 0
%ownrecord(cache fm)%name cache == nil

%externalroutine directory initialise cache
   %integer x
      %if FS lookup("FS__DIRECTORY_CACHE", x) %start
         !! printstring("Directory cache already known at ")
         !! phex(x);  newline
         cache == record(x)
      %else
         x = global heap get(size of(cache))
         cache == record(x);  cache = 0
         setup semaphore(cache_semaphore)
         signal semaphore(cache_semaphore)
         FS insert("FS__DIRECTORY_CACHE", x)
         !! printstring("Directory cache created at ")
         !! phex(addr(x));  newline
      %finish
%end

%integerfn find cache slot(%integer ID)
   %constinteger directory cache loop = cache slots - 1
   %record(cache slot fm)%name s
   %integer x, i, oldest = infinity
   %label IDL, found it
      ! First off, search the cache for the header.  This loop is based on the
      ! one used by the partition cache manager.  We assume the semaphore has
      ! been claimed elsewhere.
      x = addr(cache_ID(1))
      D1 = ID                 { The directory ID we're looking for
      *move.l x, A0           { The address of the first ID
      *move.l #directory cache loop, D0
  IDL:*cmp.l (A0)+, D1        { Compare file IDs
      *dbeq D0, IDL           { Test comparison, decrement & loop
      ! NB: for the DBcc instruction, the cc tests true for NOT performing
      ! the decrement and branch!!  Either the cc test has succeeded, or we've
      ! reached the end of the loop.  The cc are still the same so we can just
      ! perform the appropriate conditional branch....
      *beq found it           { Test was true, we've found the entry
      %result = -1            { Not there

found it:
      *move.l A0, x
      %result = (x - addr(cache_ID(1))) >> 2
%end

%predicate same(%string(*)%name a, b)
   %integer l, m, p, q, d
   %bytename aa, bb
      l = length(a);  m = length(b)
      %false %if l # m
      aa == charno(a, 1);  bb == charno(b, 1)
      %while l > 0 %and m > 0 %cycle
         %if aa # bb %start
            ! Standardise case for comparison
            %if 'A' <= aa <= 'Z' %then p = aa - 'A' + 'a' %else p = aa
            %if 'A' <= bb <= 'Z' %then q = bb - 'A' + 'a' %else q = bb
            %false %if p # q
         %finish
         l = l - 1;  m = m - 1
         aa == aa [1];  bb == bb [1]
      %repeat
      %true
%end

%integerfn cache find(%integer directory ID, %string(*)%name name)
   %record(cache slot fm)%name s
   %record(cache entry fm)%name e
   %integer slot, i, x
      directory initialise cache %if cache == nil
      !! printstring("Cache find """);  printstring(name)
      !! printstring(""" in ");  phex(directory ID);  newline
      %result = -1 %unless 0 < length(name) <= cache name limit
      semaphore wait(cache_semaphore)
      slot = find cache slot(directory ID)
      %if slot < 0 %start
         ! Directory isn't there
         cache_misses = cache_misses + 1
         signal semaphore(cache_semaphore)
         !! printstring("Directory isn't in cache");  newline
         %result = -1
      %finish
      s == cache_s(slot)
      %for i = 1, 1, s_n %cycle
         e == s_e(i)
         %if same(name, e_name) %start
            !! printstring("Found it -> ");  phex(e_ID);  newline
            x = e_ID
            cache stamp = cache stamp + 1
            s_stamp = cache stamp
            cache_hits = cache_hits + 1
            signal semaphore(cache_semaphore)
            %result = x
         %finish
      %repeat
      !! printstring("Not there");  newline
      cache_misses = cache_misses + 1
      signal semaphore(cache_semaphore)
      %result = -1
%end

%routine cache insert(%integer directory ID,
                      %string(*)%name name, %integer value)
   %record(cache slot fm)%name s
   %record(cache entry fm)%name e
   %integer slot, i, x, oldest = infinity
      directory initialise cache %if cache == nil
      !! printstring("Cache insert """);  printstring(name)
      !! printstring(""" in ");  phex(directory ID)
      !! printstring(" as ");  phex(value);  newline
      %return %unless 0 < length(name) <= cache name limit
      semaphore wait(cache_semaphore)
      slot = find cache slot(directory ID)
      %if slot < 0 %start
         ! Directory isn't there
         !! printstring("Directory isn't in cache");  newline
         %for i = 1, 1, cache slots %cycle
            s == cache_s(i)
            slot = i %and oldest = s_stamp %if s_stamp < oldest
         %repeat
         cache_ID(slot) = directory ID
         cache_s(slot)_n = 0
         cache_new dirs = cache_new dirs + 1
      %finish
      s == cache_s(slot)
      %for i = 1, 1, s_n %cycle
         e == s_e(i)
         %if same(name, e_name) %start
            !! printstring("Found it");  newline
            e_ID = value
            cache stamp = cache stamp + 1
            s_stamp = cache stamp
            cache_hits = cache_hits + 1
            signal semaphore(cache_semaphore)
            %return
         %finish
      %repeat
      !! printstring("Not there");  newline
      cache_new entries = cache_new entries + 1
      %if s_n = cache slot entries %start
         !! printstring("Full");  newline
         signal semaphore(cache_semaphore)
         %return
      %finish
      s_n = s_n + 1
      e == s_e(s_n)
      e_name = name;  e_ID = value
      signal semaphore(cache_semaphore)
%end

%routine cache delete(%integer directory ID, %string(*)%name name)
   %record(cache slot fm)%name s
   %record(cache entry fm)%name e
   %integer slot, i, x
      directory initialise cache %if cache == nil
      !! printstring("Cache delete """);  printstring(name)
      !! printstring(""" in ");  phex(directory ID);  newline
      %return %unless 0 < length(name) <= cache name limit
      semaphore wait(cache_semaphore)
      slot = find cache slot(directory ID)
      %if slot < 0 %start
         ! Directory isn't there
         !! printstring("Directory isn't in cache");  newline
         cache_misses = cache_misses + 1
         signal semaphore(cache_semaphore)
         %return
      %finish
      s == cache_s(slot)
      %for i = 1, 1, s_n %cycle
         e == s_e(i)
         %if same(name, e_name) %start
            !! printstring("Found it");  newline
            %if i # s_n %start
               ! Not at the end, so we'll have to shuffle
               s_e(x - 1) = s_e(x) %for x = i + 1, 1, s_n
            %finish
            ! All done, forget the last slot and return
            s_n = s_n - 1
            cache_hits = cache_hits + 1
            signal semaphore(cache_semaphore)
            %return
         %finish
      %repeat
      !! printstring("Not there");  newline
      cache_misses = cache_misses + 1
      signal semaphore(cache_semaphore)
%end


! Root directory stuff

%owninteger root directory ID = -1
%constinteger root ID = 16_01000002;  ! No sequence

%routine get root ID
   %integer status, x, token, flags
      fsys initialise
      status = fsys get full ID(root ID, root directory ID)
      %if status # 0 %start
         printstring("Failed to find root directory's ID: ")
         write(status, 0);  newline
         prompt("Would you like to create a new one? ")
         read symbol(x) %until x # NL
         %stop %unless x = 'y' %or x = 'Y'
         status = B tree create(nil, 0, "Root", 1, 0, root directory ID)
         %if status # 0 %start
            printstring("Failed to create new root ");  write(status, 0)
            newline
            %stop
         %finish
         printstring("New root directory is ");  phex(root directory ID)
         newline
         ! Bump the refcount.  It's not actually IN any directories,
         ! but it'll stop it being deleted.
         status = fsys bump refcount(nil, root directory ID, 0, 1)
         %if status # 0 %start
            printstring("Failed to bump refcount for new root");  newline
         %finish
         ! Now insert $BootArea
         prompt("Would you like to insert $BootArea as 04000000? ")
         read symbol(x) %until x # NL
         %if x = 'y' %or x = 'Y' %start
            status = B tree open by ID(nil, 0, root directory ID, 1, token, flags)
            %if status # 0 %start
               printstring("Failed to open new root directory: ")
               write(status, 0);  newline
            %else
               status = B tree add entry(token, "$BootArea", 16_04000000)
               %if status # 0 %start
                  printstring("Failed to insert $BootArea: ")
                  write(status, 0);  newline
               %finish
               status = B tree close(token, status)
            %finish
         %finish
      %finish
      root directory ID = root directory ID ! directory flag
      !! printstring("Root directory is ");  phex(root directory ID)
      !! newline
%end


! Diagnostic string-putter

!! %routine zput string(%string(255) s)
!!    %integer i, ch
!!       %return %if s = ""
!!       %for i = 1, 1, length(s) %cycle
!!          ch = charno(s, i)
!!          %if ' ' <= ch <= '~' %start
!!             print symbol(ch)
!!          %else
!!             print symbol('<')
!!             write(ch, 0)
!!             print symbol('>')
!!          %finish
!!       %repeat
!! %end

!! %routine print key list(%record(key list fm)%name list)
!!    %while list ## nil %cycle
!!       spaces(15 - length(list_key));   zput string(list_key)
!!       spaces(2);  phex(list_value);  newline
!!       list == list_next
!!    %repeat
!! %end


%predicate path component OK(%string(*)%name s)
   %integer i
      %false %if s = ""
      %for i = 1, 1, length(s) %cycle
         %false %unless ' ' < charno(s, i) <= '~'
      %repeat
      %true
%end

%externalintegerfn directory lookup one(%record(fsys access fm)%name access,
                                        %integer request flags,
                                        %integer directory ID,
                                        %string(*)%name key, %integer version,
                                        %integername resulting ID,
                                        %string(*)%name textual translation)
   %record(compound fm) c
   %integer token, status, size, x, flags, drive mask
      get root ID %if root directory ID < 0
      directory ID = root directory ID %if directory ID = 0
      !! printstring("Lookup one : component ");  zput string(key)
      !! printstring(" in ");  phex(directory ID);  newline
      %result = not a directory error %if directory ID & directory flag = 0
      %result = version not found error %if version > 0;  ! No absolute versions
      %if key = "" %start
         ! Null name -> directory self-reference
         resulting ID = directory ID
         %result = 0
      %else %if length(key) = 5 %and charno(key, 1) = ID escape character
         ! Absolute ID escape in path.  First character is escape, next
         ! four are (binary) ID.
         resulting ID = integer(addr(key) + 2)
         !! printstring("Absolute lookup: ");  phex(resulting ID);  newline
         %result = 0
      %finish
      %result = dud component error %unless path component OK(key)
      %if version = 0 %start
         ! Only the most recent is ever cached
         x = cache find(directory ID, key)
         %if x > 0 %start
            resulting ID = x
            flags = 0;  ! Zap world-read, so no cache insert
            -> done ID
         %finish
      %finish
      status = B tree open by ID(access, request flags, directory ID,
                                 0, token, flags)
      textual translation = key %and %result = status %if status # 0
      status = B tree find entry(token, key, resulting ID)
      %if status # 0 %start
         x = B tree close(token, 0)
         !! %if x # 0 %start
         !!    printstring("Lookup one: close status ")
         !!    write(x, 0);  newline
         !! %finish
         textual translation = key
         %result = status
      %finish
      %if resulting ID & non ID flag = 0 %start
         ! Simple ID
         x = B tree close(token, 0)
         !! %if x # 0 %start
         !!    printstring("Lookup one: close status ")
         !!    write(x, 0);  newline
         !! %finish
         %result = version not found error %unless version = 0
done ID: %if resulting ID & 16_3F000000 = 0 %start
            ! Drive and partition defaulted
            resulting ID = resulting ID ! (directory ID & 16_3F000000)
         %else %if resulting ID & 16_30000000 = 0
            ! Drive only defaulted
            resulting ID = resulting ID ! (directory ID & 16_30000000)
         %finish
         textual translation = ""
         !! printstring("Result is ");  phex(resulting ID);  newline
         ! Should we insert it in the cache?  Only the latest version of world-
         ! readable files are inserted.  Note that when we reach here either
         ! (a) version = 0 and the ID was non-compound, or (b) version has been
         ! converted to be the compound slot number (1, 2, 3, ...).  Note that
         ! the case where the caller is looking for version < 0 with a simple
         ! ID is an error and has already been bounced (just before the label
         ! above).
         cache insert(directory ID, key, resulting ID) %c
            %if version <= 1 %and flags & world read access # 0
         %result = 0
      %else
         ! Compound ID
         status = B tree data value(token, resulting ID & (\ non ID flag),
                                    size, c)
         x = B tree close(token, 0)
         !! %if x # 0 %start
         !!    printstring("Lookup one: close status ")
         !!    write(x, 0);  newline
         !! %finish
         %if status # 0 %start
            textual translation = key
            %result = status
         %finish
         %if c_tag = compound ID tag %start
            version = 1 - version
            %result = version not found error %unless 0 < version <= c_count
            resulting ID = c_ID(version)
            -> done ID
         %else
            ! Redirector
            !! printstring("Redirector");  newline
            textual translation = c_text
            %result = c_tag
         %finish
      %finish
%end

! Search for the specified path.  Halts search at last-but-one component.
! Returns: file ID (> 0) if a local filename; or component count + translation
! if non-local; or component count + offending component + error status.
! Always start looking in the file system root directory.

%externalrecord(path fm)%map directory penultimate %c
                                    (%record(fsys access fm)%name access,
                                     %integer request flags,
                                     %record(path fm)%name path, 
                                     %integername components translated,
                                     %integername resulting ID,
                                     %string(*)%name textual translation,
                                     %integername status)
   %integer ID, current directory ID
      status = dud path error %and %result == nil %if path == nil
      get root ID %if root directory ID < 0
      current directory ID = root directory ID
      ID = root directory ID
      components translated = 0
      %cycle
         %if path_next == nil %start
            ! Stop at the penultimate one
            !! printstring("Lookup terminating with ");  phex(ID);  newline
            resulting ID = ID
            textual translation = ""
            status = 0
            %result == path
         %finish
         current directory ID = ID
         !! printstring("Lookup: component ");  zput string(path_key)
         !! printstring(" in ");  phex(current directory ID);  newline
         status = directory lookup one(access, request flags, current directory ID,
                                       path_key, 0 {not until final},
                                       ID, textual translation)
         resulting ID = 0 %and %result == nil %if status < 0;  ! Not there
         %result == path %if status > 0;  ! Non-local
         components translated = components translated + 1
         path == path_next
      %repeat
%end

%externalintegerfn directory lookup(%record(fsys access fm)%name access,
                                    %integer request flags,
                                    %record(path fm)%name path,
                                    %integername components translated,
                                    %integername file ID, penultimate ID,
                                    %string(*)%name textual translation)
   %integer status
      !! printstring("Directory lookup");  newline
      path == directory penultimate(access, request flags, path,
                                    components translated,
                                    penultimate ID, textual translation, status)
      %result = status %if status # 0
      status = directory lookup one(access, request flags, penultimate ID,
                                    path_key, path_version,
                                    file ID, textual translation)
      components translated = components translated + 1 %if status = 0
      !! printstring("Returning file ");  phex(file ID)
      !! printstring(", penultimate ");  phex(penultimate ID)
      !! printstring(", status ");  write(status, 0);  newline
      %result = status
%end


%integerfn insert new compound ID(%integer token, previous ID, new ID,
                                  %integername resulting)
   %record(compound fm) c
   %integer status, size, i
      !! printstring("Insert new compound ID: ");  phex(new ID)
      !! printstring(", previous: ");  phex(previous ID);  newline
      %if previous ID & non ID flag = 0 %start
         ! Inserting new with previously simple
         !! printstring("New + previously simple: ")
         !! phex(previous ID);  newline
         %result = no versions error %c
            %if previous ID & (directory flag ! non ID flag) # 0 %c
            %or new ID & (directory flag ! non ID flag) # 0
         c = 0
         c_tag = compound ID tag
         c_count = 2
         c_ID(1) = new ID
         c_ID(2) = previous ID
         %result = B tree data insert(token, 12, c, resulting)
      %else
         ! Previously compound
         %result = no versions error %c
            %if new ID & (directory flag ! non ID flag) # 0;  ! Require simple
         previous ID = previous ID & (\ non ID flag)
         !! printstring("New + previous compound @ ")
         !! phex(previous ID);  newline
         status = B tree data value(token, previous ID, size, c)
         %result = status %if status # 0
         %result = no versions error %unless c_tag = compound ID tag
         %result = too many versions error %if c_count = max compound ID
         c_count = c_count + 1
         c_ID(i) = c_ID(i - 1) %for i = c_count, -1, 2
         c_ID(1) = new ID
         status = B tree data insert(token, 4 * (c_count + 1), c, resulting)
         %result = status %if status # 0
         %result = B tree data delete(token, previous ID)
      %finish
%end

%externalintegerfn directory insert ID(%record(fsys access fm)%name access,
                                       %integer request flags,
                                       %integer directory ID,
                                       %string(*)%name inserting key,
                                       %integer inserting ID)
   %integer status, ID, token, x, flags, resulting, bump ID
      %result = dud component error %if inserting ID & 16_0F000000 = 0
      %result = dud component error %unless path component OK(inserting key)
      %result = not a directory error %if directory ID & directory flag = 0
      inserting ID = inserting ID & (\ directory relative flag);  ! Obsolete
      bump ID = inserting ID
      %if inserting ID & 16_3F000000 = directory ID & 16_3F000000 %start
         ! Drive and partition same, so default them
         inserting ID = inserting ID & (16_00FFFFFF ! directory flag)
      %else %if inserting ID & 16_30000000 = directory ID & 16_30000000
         ! Drive only same -- default it
         inserting ID = inserting ID & (16_0FFFFFFF ! directory flag)
      %finish
      !! printstring("Inserting ");  phex(inserting ID)
      !! printstring(" into ");  phex(directory ID);  newline
      status = B tree open by ID(access, request flags, directory ID,
                                 1, token, flags)
      %result = status %if status # 0
      status = B tree find entry(token, inserting key, ID)
      %if status = 0 %start
         ! Entry is already there, so compound it.
         %if inserting ID & directory flag # 0 %start
            ! Not allowed to insert a directory as compound
            status = B tree close(token, -1)
            %result = no versions error
         %finish
         cache delete(directory ID, inserting key)
         status = insert new compound ID(token, ID, inserting ID, resulting)
         status = B tree modify entry(token, inserting key,
                                      resulting ! non ID flag) %if status = 0
      %else
         ! Not there, so insert a new simple ID
         status = B tree add entry(token, inserting key, inserting ID)
      %finish
      x = B tree close(token, status)
      !! %if x # 0 %start
      !!    printstring("Insert ID: close status ")
      !!    write(x, 0);  newline
      !! %finish
      %result = status %if status # 0
      status = fsys bump refcount(access, bump ID, request flags, 1)
      %result = status %if status # 0
      cache insert(directory ID, inserting key, inserting ID) %c
         %if flags & world read access # 0
      %result = 0
%end

%externalintegerfn directory insert textual(%record(fsys access fm)%name access,
                                            %integer request flags,
                                            %integer directory ID,
                                            %string(*)%name inserting key,
                                            %integer inserting level,
                                            %string(*)%name inserting text)
   %integer status, ID, token, x, flags, resulting
   %record(compound fm) c
      %result = dud component error %unless path component OK(inserting key)
      %result = not a directory error %if directory ID & directory flag = 0
      %result = dud redirect level error %if inserting level < 2
      !! printstring("Inserting ");  printstring(inserting text)
      !! printstring(" into ");  phex(directory ID)
      !! printstring(" as ");  printstring(inserting key)
      !! printstring(", level ");  write(inserting level, 0);  newline
      status = B tree open by ID(access, request flags, directory ID,
                                 1, token, flags)
      %result = status %if status # 0
      status = B tree find entry(token, inserting key, ID)
      %if status = 0 %start
         ! Entry is already there & versions aren't allowed
         x = B tree close(token, -1)
         !! %if x # 0 %start
         !!    printstring("Insert textual: close status ")
         !!    write(x, 0);  newline
         !! %finish
         %result = no versions error
      %finish
      ! Not there, so insert a new textual translation
      c = 0
      c_tag = inserting level;  ! Assume correct
      c_text = inserting text
      status = B tree data insert(token, length(inserting text) + 3,
                                  c, resulting)
      %if status # 0 %start
         x = B tree close(token, -1)
         !! %if x # 0 %start
         !!    printstring("Insert textual: close status ")
         !!    write(x, 0);  newline
         !! %finish
         %result = status
      %finish
      !! printstring("Data inserted as ");  phex(resulting);  newline
      status = B tree add entry(token, inserting key, resulting ! non ID flag)
      x = B tree close(token, status)
      !! %if x # 0 %start
      !!    printstring("Insert textual: close status ")
      !!    write(x, 0);  newline
      !! %finish
      %result = status
%end

%externalintegerfn directory delete entry(%record(fsys access fm)%name access,
                                          %integer request flags,
                                          %integer directory ID,
                                          %string(255) key, %integer version)
   %integer status, token, flags, translated ID, bump status, x
   %integer size, site, new site, i
   %record(compound fm) c
      %result = dud component error %unless path component OK(key)
      %result = not a directory error %if directory ID & directory flag = 0
      !! printstring("Deleting ");  printstring(key)
      !! printstring(" from ");  phex(directory ID);  newline
      cache delete(directory ID, key)
      status = B tree open by ID(access, request flags, directory ID,
                                 1, token, flags)
      %result = status %if status # 0
      status = B tree find entry(token, key, translated ID)
      %if status = 0 %start
         %if translated ID & non ID flag = 0 %start
            ! Simple translation
            status = B tree delete entry(token, key)
         %else
            ! Translation is non-simple
            site = translated ID & (\ non ID flag)
            status = B tree data value(token, site, size, c)
            %if status # 0 %start
               x = B tree close(token, -1)
               !! %if x # 0 %start
               !!    printstring("Delete entry: close status ")
               !!    write(x, 0);  newline
               !! %finish
               %result = status
            %finish
            %if c_tag # compound ID tag %start
               ! Non-ID, so just zap the entry & translation
               status = B tree data delete(token, site)
               status = B tree delete entry(token, key)
               x = B tree close(token, status)
               !! %if x # 0 %start
               !!    printstring("Delete entry: close status ")
               !!    write(x, 0);  newline
               !! %finish
               %result = status
            %finish
            version = 1 - version
            %unless 0 < version <= c_count %start
               x = B tree close(token, -1)
               !! %if x # 0 %start
               !!    printstring("Delete entry: close status ")
               !!    write(x, 0);  newline
               !! %finish
               %result = version not found error
            %finish
            translated ID = c_ID(version)
            %if version < c_count %start
               ! Shuffle down
               c_ID(i) = c_ID(i + 1) %for i = version, 1, c_count - 1
            %finish
            c_count = c_count - 1
            %if c_count = 1 %start
               status = B tree modify entry(token, key, c_ID(1))
               %if status # 0 %start
                  x = B tree close(token, -1)
                  !! %if x # 0 %start
                  !!    printstring("Delete entry: close status ")
                  !!    write(x, 0);  newline
                  !! %finish
                  %result = status
               %finish
               status = B tree data delete(token, site)
            %else
               status = B tree data insert(token, 4 * (c_count + 1), c, new site)
               %if status # 0 %start
                  x = B tree close(token, -1)
                  !! %if x # 0 %start
                  !!    printstring("Delete entry: close status ")
                  !!    write(x, 0);  newline
                  !! %finish
                  %result = status
               %finish
               status = B tree modify entry(token, key,
                                            new site ! non ID flag)
               %if status # 0 %start
                  x = B tree close(token, -1)
                  !! %if x # 0 %start
                  !!    printstring("Delete entry: close status ")
                  !!    write(x, 0);  newline
                  !! %finish
                  %result = status
               %finish
               status = B tree data delete(token, site)
            %finish
         %finish
      %finish
      x = B tree close(token, status)
      !! %if x # 0 %start
      !!    printstring("Delete entry: close status ")
      !!    write(x, 0);  newline
      !! %finish
      %if translated ID & 16_3F000000 = 0 %start
         ! Drive and partition defaulted
         translated ID = translated ID ! (directory ID & 16_3F000000)
      %else %if translated ID & 16_30000000 = 0
         ! Drive only defaulted
         translated ID = translated ID ! (directory ID & 16_30000000)
      %finish
      bump status = fsys bump refcount(access, translated ID, request flags, -1)
      %result = bump status %if bump status # 0
      %result = status
%end


%externalintegerfn create directory(%record(fsys access fm)%name access,
                                    %integer request flags,
                                    %record(path fm)%name path,
                                    %integer desired partition,
                                    %integer no inherit,
                                    %integername components translated,
                                    %integername final directory ID,
                                    %string(*)%name textual translation)
   %record(path fm)%name final
   %integer status, ID, token, x, actual partition, flags, benefactor ID
      final == directory penultimate(access, request flags, path, components translated,
                                     final directory ID, textual translation,
                                     status)
      %result = status %if status # 0
      %result = not a directory error %if final directory ID & directory flag = 0
      %if desired partition > 0 %start
         %result = no privilege error %if access_privileges & admin privilege = 0
         actual partition = desired partition
      %else
         actual partition = (final directory ID & partition mask) >> partition shift
      %finish
      !! printstring("Creating directory ");  printstring(final_key)
      !! printstring(" in ");  phex(final directory ID)
      !! printstring(", partition ");  write(actual partition, 0);  newline
      ! Note that when we create the directory below we use its parent as
      ! its benefactor, so that directories inherit ownership, protection
      ! and so on from the directory they were created in.  This has the
      ! side effect of requiring modify access to the parent directory
      ! (because of FSys's version replacement check), but that's OK as we
      ! are going to be trying to modify the parent directory anyway...
      benefactor ID = final directory ID
      benefactor ID = 0 %if no inherit # 0
      status = B tree create(access, request flags, final_key, actual partition,
                             benefactor ID, ID)
      !! printstring("Create status ");  write(status, 0);  newline
      %result = status %if status # 0
      ID = ID ! directory flag
      ! Bump the refcount
      status = fsys bump refcount(access, ID, request flags, 1)
      !! printstring("Bump status ");  write(status, 0);  newline
      %if status # 0 %start
         ! For some reason we don't have access to our own newly-created
         ! directory, so we'll just (attempt to) delete it again.
         x = fsys delete file(nil, ID, 0)
         %result = status
      %finish
      ! Now insert in the final directory
      %if ID & 16_3F000000 = final directory ID & 16_3F000000 %start
         ! Drive and partition same, so default them
         ID = ID & (16_00FFFFFF ! directory flag)
      %else %if ID & 16_30000000 = final directory ID & 16_30000000
         ! Drive only same -- default it
         ID = ID & (16_0FFFFFFF ! directory flag)
      %finish
      status = B tree open by ID(access, request flags, final directory ID,
                                 1, token, flags)
      %result = status %if status # 0
      status = B tree find entry(token, final_key, x)
      %if status = 0 %start
         ! Directory already exists, so we'll have to delete the new one
         x = B tree close(token, -1)
         !! %if x # 0 %start
         !!    printstring("Create directory: close status ")
         !!    write(x, 0);  newline
         !! %finish
         x = fsys delete file(nil, ID, 0)
         %result = no versions error
      %finish
      status = B tree add entry(token, final_key, ID)
      x = B tree close(token, status)
      !! %if x # 0 %start
      !!    printstring("Create directory: close status ")
      !!    write(x, 0);  newline
      !! %finish
      %result = status
%end

%externalintegerfn directory check empty(%record(fsys access fm)%name access,
                                         %integer request flags,
                                         %integer directory ID)
   %integer token, flags, status, empty
      %result = not a directory error %if directory ID & directory flag = 0
      status = B tree open by ID(access, request flags, directory ID,
                                 0, token, flags)
      %result = status %if status # 0
      %if flags & multiple references # 0 %or B tree empty(token) %start
         ! Directories with multiple references to them are always
         ! assumed to be empty (so all but one can be deleted).
         empty = 0
      %else
         empty = non empty directory error
      %finish
      status = B tree close(token, 0)
      !! %if status # 0 %start
      !!    printstring("Check empty: close status ")
      !!    write(status, 0);  newline
      !! %finish
      %result = empty
%end


%externalrecord(*)%map directory contents(%record(fsys access fm)%name access,
                                          %integer request flags,
                                          %integer ID, %integername status, flags)
   %record(key list fm)%name key list, last == nil, current, next
   %integer token, x, i, size
   %record(compound fm) c
   %string(255) text
      %if root directory ID < 0 %start
         printstring("*** Calling ""directory contents"" before a lookup??")
         newline
         status = dud path error
         %result == nil
      %finish
      status = not a directory error %and %result == nil %c
         %if ID & directory flag = 0
      !! printstring("Get key list for ");  phex(ID);  newline
      status = B tree open by ID(access, request flags, ID, 0, token, flags)
      %result == nil %if status # 0
      key list == B tree key list(token, status)
      current == key list
      %while current ## nil %cycle
         %if current_value & non ID flag = 0 %start
            last == nil
         %else
            ! Compound with (maybe) versions)
            status = B tree data value(token, current_value & (\ non ID flag),
                                       size, c)
            %if status # 0 %start
               ! Error on lookup, so just assume it's simple
               last == nil
               status = 0;  !!!
               current_value = non ID flag
            %else %if c_tag = compound ID tag
               text = current_key
               next == current_next
               last == current
               current_value = c_ID(1)
               %for i = 2, 1, c_count %cycle
                  current == record(heap get(length(text) + 9 + 4))
                  current_value = c_ID(i)
                  current_key = text . to string(NUL) . itos(1 - i, 0)
                  current_next == next
                  last_next == current
                  last == current
               %repeat
            %finish
         %finish
         current == current_next
      %repeat
      x = B tree close(token, 0)
      !! %if x # 0 %start
      !!    printstring("Directory contents: close status ")
      !!    write(x, 0);  newline
      !! %finish
      !! print key list(key list)
      %result == key list
%end

%end %of %file
