%include "inc:util.imp"
%include "system:disqio.inc"

%begin; !Filestore Boot Area Manipulator

! Revised version: directory is now two blocks long, and there are
! two independent boot areas, each 1024 blocks long.

! 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 (256 32-bit words):
! First longword is a checksum such that the sum of all 256 longwords is 0.
! Remainder comprises of 42 24-byte records plus an extra longword which is
! treated as part of a 43rd 24-byte record.  This longword must be 0.

! Each of the 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.

%constinteger maxslot=42,dirlongs=256
%recordformat file f (%integer size, %string(19) name)
%recordformat dir f ((%integer sum, %record(filef)%array file(1:maxslot+1)) %c
                    %or %integerarray w(1:dirlongs))
%record(filef)%name file
%integer filepos
%record(dirf)dir
%integer dir xno = 0, offset = 0

%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
%end

%routine open boot area
%end

%routine bootread(%integer byte,buffer)
%integername n
  n == transfer(dread,512,byte+offset,integer(buffer))
  %cycle
  %repeatuntil n#0
%end

%routine bootwrite(%integer byte,buffer)
%integername n
  n == transfer(dwrite,512,byte+offset,integer(buffer))
  %cycle
  %repeatuntil n#0
%end

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

%routine write dir
%integer i,a,sum=0
  sum = sum-dir_w(i) %for i = 2,1,dirlongs
  dir_sum = sum
  a = addr(dir)
  bootwrite(0,a); bootwrite(512,a+512)
  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,maxslot
    %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(maxslot)_size#0
    dir_file(i+1) = dir_file(i) %for i = maxslot-1,-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 comparefile(%string(19)boot,normal)
%bytearray data(1:512)
%integer start,length,pos,i
  find(1,boot)
  connectfile(normal,0,start,length)
  pos = file_size-length
  %unless pos=0 %start
    printstring(boot." is ")
    write(|pos|,0); printstring(" bytes ")
    %if pos>0 %then printstring("longer") %else printstring("shorter")
    printstring(" than ".normal)
    newline; %return
  %finish
  pos = 0
  %cycle
    bootread(filepos+pos,addr(data(1)))
    %for i = 1,1,512 %cycle
      %if pos+i>length %start
        printstring(boot." and ".normal." are identical")
        newline; %return
      %finish
      %unless byteinteger(start+pos+i-1)=data(i) %start
        printstring(boot."="); phex2(data(i))
        printstring(" and ".normal."="); phex2(byteinteger(start+pos+i-1))
        printstring(" differ at byte "); write(pos+i-1,0)
        newline; %return
      %finish
    %repeat
    pos = pos+512
  %repeat
%end
  
%routine bad(%integer block,blocks)
  inject(block<<9-offset,blocks<<9,"BadArea")
%end

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

%routine files
%integer i,pos=0
  read dir
  %for i = 1,1,maxslot %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 look at key
@16_400031 %byte key
  printstring("Default area "); printsymbol(key&1+'0')
  offset = (key&1)<<(10+9)
  newline
%end
  
%routine help
  printstring("A - Select Area          (0 or 1)"); newline
  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("C - Compare File         (Boot-source,Normal-source"); 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
  %routine readother
    read(t); t = s %if t="="
  %end
  %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)
      %if i>1024 %and offset=0 %start
        printstring("That would overwrite the second directory"); newline
      %else
        init(i)
      %finish
    %elseif sym='a'
      prompt("Area:"); read(i)
      close boot area
      offset = (i&1)<<(10+9)
      printstring("Using area "); printsymbol(i&1+'0'); newline
    %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:"); read(s)
      prompt("To:"); readother
      writefile(s,t)
    %elseif sym='r'
      prompt("From:"); read(s)
      prompt("To:"); readother
      readfile(s,t)
    %elseif sym='c'
      prompt("Boot area file:"); read(s)
      prompt("File system file:"); readother
      comparefile(s,t)
    %elseif sym='d'
      prompt("File:")
      read(s); delete(s)
    %elseif sym='n'
      prompt("File:"); read(s)
      prompt("Newname:"); read(t)
      rename(s,t)
    %else
      help
    %finish
    selectinput(0); prompt(">")
    readsymbol(sym) %until sym=nl
  %repeat
%end

look at key
mmi
close boot area

%endofprogram
