%begin; !Filestore Boot Area Manipulator

! The boot area is a contiguous collection of 512-byte disk blocks,
! which contains textually named files, suitable for reading into
! the controlling machine's memory using the LOADFILE routine in ROM.
! The first block is used as a directory.

! Directory layout (128 32-bit words):
! First word is a checksum such that the sum of all 128 words is 0.
! Remainder comprises of 21 24-byte records plus an extra word which is
! treated as part of a 22nd 24-byte record.  This last word must be 0.

! Each of the 21 file records is a size-name pair.  Null names denote
! free space.  Zero size denotes the end of the file-record-list.  The
! sizes are in bytes.  All chunks start at block boundaries.  The position
! of file n+1 is given by that of file n, plus the size of file n, rounded
! up to the next block boundary.  Sizes of free chunks are multiples of the
! block size.

%recordformat file f (%integer size, %string(19) name)
%recordformat dir f ((%integer sum, %record(filef)%array file(1:22)) %c
                    %or %integerarray w(1:128))
%record(filef)%name file
%integer filepos
%record(dirf)dir
%integer dir xno = 0

%include "i:fs.inc"
%include "i:util.inc"

%routine error(%string(255)s)
  selectoutput(0); printstring(s); newline
  %signal 3
%end

%routine hdhtos(%integer n,%string(*)%name s)
  s = tostring(n>>8+'0')
  s = s.tostring(n>>4&15+'0')
  s = s.tostring(n&15+'0')
%end

%routine close boot area
%integer x
  x = fcomm('K0'+dirxno,"") %unless dirxno=0
  dir xno = 0
%end

%routine open boot area
  dir xno = fcomm('A'<<8,"$:BOOTAREA") %if dirxno=0
%end

%routine bootread(%integer byte,buffer)
%string(3)b
%integer n
  hdhtos(byte>>9,b)
  n = fcommr('R0'+dirxno,b,byteinteger(buffer),512)
  %unless n=512 %start
    printstring("R:"); write(n,1); newline
  %finish
%end

%routine bootwrite(%integer byte,buffer)
%string(3)b
  hdhtos(byte>>9,b); b = b.","
  fcommw('W0'+dirxno,b,byteinteger(buffer),512)
%end

%routine read dir
%integer i,sum=0
  %returnunless dirxno=0
  open boot area
  dir = 0
  bootread(0,addr(dir))
  sum = sum+dir_w(i) %for i = 1,1,128
  error("Directory checksum error") %unless sum=0
%end

%routine write dir
%integer i,sum=0
  sum = sum-dir_w(i) %for i = 2,1,128
  dir_sum = sum
  bootwrite(0,addr(dir))
  close bootarea
%end

%predicate same(%string(*)%name a,b)
%integer i,j,k
  i = length(a); %falseunless i=length(b)
  %while i>0 %cycle
    j = charno(a,i); k = charno(b,i)!!j
    %if k#0 %start
      %falseunless k=32 %and 'a'<=j!32<='z'
    %finish
    i = i-1
  %repeat
  %true
%end

%routine find(%integer minsize,%string(19)name)
%integer i,j
  read dir
  filepos = 0; i = 1
  %cycle
    file == dir_file(i); j = file_size
    error(name." not found") %if j<=0
    %returnif j>=minsize %and same(file_name,name)
    filepos = filepos+((j+511)&\511)
    i = i+1
  %repeat
%end

%routine rename(%string(19)s,t)
  find(1,s); file_name = t; write dir
%end

%routine delete(%string(19)s)
%integer i,j
%record(filef)%name f,g
  find(1,s)
  file_size = (file_size+511)&\511; file_name = ""
  i = 1
  %cycle
    f == dir_file(i); g == dir_file(i+1)
    %exitif f_size=0 %or g_size=0
    %if f_name="" %and g_name="" %start
      f_size = f_size+g_size
      dir_file(j) = dir_file(j+1) %for j = i+1,1,21
    %finishelse i = i+1
  %repeat
  write dir
%end

%routine inject(%integer pos,size,%string(19)name)
%integer i,low,high,rest
%record(filef)%name f,g,h
  %record(filef)%map elbow(%integer p)
  %integer i
    error("No slot") %if dir_file(21)_size#0
    dir_file(i+1) = dir_file(i) %for i = 20,-1,p
    %result == dir_file(p)
  %end
  read dir
  i = 1; low = 0
  %cycle
    f == dir_file(i)
    error("Cannot insert ".name) %if f_size=0
    high = low+f_size
    %if low<=pos<high %and high-low>=size %and f_name="" %start
      %if low=pos %start
        %unless pos+((size+511)&\511)=high %start
          g == elbow(i+1); g = 0
          g_size = f_size-((size+511)&\511)
        %finish
        f_size = size; f_name = name
      %else
        g == elbow(i+1); g = 0
        h == elbow(i+2); h = 0
        f_size = pos-low
        g_size = size; g_name = name
        h_size = high-pos-((size+511)&\511)
      %finish
      %exit
    %finish
    high = low+((f_size+511)&\511); low = high
    i = i+1
  %repeat
  write dir
%end

%routine writefile(%string(19)from,to)
%integer start,length,pos
  connectfile(from,0,start,length)
  find(length,"")
  pos = 0
  %cycle
    bootwrite(filepos+pos,start+pos)
    pos = pos+512
  %repeatuntil pos>=length
  inject(filepos,length,to)
%end

%routine readfile(%string(19)from,to)
%bytearray data(1:512)
%integer pos,i
  find(1,from)
  openoutput(1,to); selectoutput(1)
  pos = 0
  %cycle
    bootread(filepos+pos,addr(data(1)))
    %for i = 1,1,512 %cycle
      printsymbol(data(i)) %if i-1+pos<file_size
    %repeat
    pos = pos+512
  %repeatuntil pos>=file_size
  closeoutput
%end

%routine bad(%integer block,blocks)
  inject(block<<9,blocks<<9,"BadArea")
%end

%routine init(%integer blocks)
  open boot area
  dir = 0
  file == dir_file(1); file_size = 512; file_name = "Directory"
  file == dir_file(2); file_size = (blocks-1)<<9
  write dir
%end

%routine files
%integer i,pos=0
  read dir
  %for i = 1,1,21 %cycle
    file == dir_file(i)
    %unless file_size=0 %and file_name="" %start
      write(i,2); space
      phex(pos); space; phex(file_size); space; printstring(file_name); newline
      pos = pos+((file_size+511)&\511)
    %finish
  %repeat
%end

%routine help
  printstring("I - Initialise directory (Blocks)"); newline
  printstring("B - Note Bad Area        (Blockno,blocks)"); newline
  printstring("F - List Files           ()"); newline
  printstring("W - Write File           (Normal-source,Boot-dest)"); newline
  printstring("R - Read File            (Boot-source,Normal-dest)"); newline
  printstring("D - Delete File          (Boot)"); newline
  printstring("N - Rename File          (Oldboot,Newboot)"); newline
  printstring("E - Exit                 ()"); newline
%end

%routine mmi
%integer sym,i,j
%string(19)s,t
  %onevent 3,9 %start
    selectoutput(0)
    write(event_event,0); write(event_sub,1); write(event_extra,1); space
    printstring(event_message); newline
  %finish
  selectoutput(0); selectinput(0); prompt(">")
  %cycle
    selectoutput(0)
    readsymbol(sym) %and sym = sym!32 %until 'a'<=sym<='z'
    %exitif sym='e'
    %if sym='i' %start
      prompt("Size(blocks) of boot area:")
      read(i); init(i)
    %elseif sym='b'
      prompt("Bad area at block:"); read(i)
      prompt("Size(blocks):"); read(j)
      bad(i,j)
    %elseif sym='f'
      files
    %elseif sym='w'
      prompt("From:"); readstring(s)
      prompt("To:"); readstring(t)
      writefile(s,t)
    %elseif sym='r'
      prompt("From:"); readstring(s)
      prompt("To:"); readstring(t)
      readfile(s,t)
    %elseif sym='d'
      prompt("File:")
      readstring(s); delete(s)
    %elseif sym='n'
      prompt("File:"); readstring(s)
      prompt("Newname:"); readstring(t)
      rename(s,t)
    %else
      help
    %finish
    selectinput(0); prompt(">")
    readsymbol(sym) %until sym=nl
  %repeat
%end

mmi
close boot area

%endofprogram
