%option "-nodiag-nocheck"
!!bodily %include "managr:newadmin.inc"
%include "inc:util.imp"
%include "inc:wildmat.imp"
%externalstring(255)%fnspec finfo(%string(255)s,%integer n)

%recordformat entry fm (%record(entry fm)%name next, %string(7)id,
  %string(*)%name sur,pre,%integer group,flags,created,  {from DB}
  partition,files,blocks,age                             {from disk})

%recordformat stringlist(%record(stringlist)%name next,%string(*)%name s)

%conststring adminfile="managr:admin.db"
%ownintegerarray admin index('A'-1:'Z'+2)
%ownrecord(stringlist)%name admingrouplist==nil,adminflaglist==nil

%string(*)%map heapstring(%string(*)%name s)
! Allocate enough heap space to hold a copy of S, and copy S into it.
%string(*)%name h
  h == string(heapget(length(s)+1))
  h = s; %result == h
%end

%string(*)%map nth(%record(stringlist)%name r,%integer n)
! Return the Nth entry in list R, where N=1 gives the first.
! NULL returned for out of range N.
%ownstring(1)null=""
  %cycle
    %result == null %if r==nil %or n<=0
    %result == r_s %if n=1
    n = n-1; r == r_next
  %repeat
%end

%integerfn lookup only(%record(stringlist)%name list,%string(*)%name s)
! Search LIST for S, returning index position as result.
! Return 0 if not found.
%integer n
%string(255)x,y
  n = 1; y = s; toupper(y)
  %cycle
    %result = 0 %if list==nil
    x = list_s; toupper(x)
    %result = n %if x=y
    n = n+1; list == list_next
  %repeat
%end

%record(stringlist)%map lookup and add(%record(stringlist)%name list,
    %string(*)%name s,%integername n)
! Search LIST for S, if not found, insert it.
! Return idex position in N.
! Return possibly updated LIST as result.
%record(stringlist)%name cell==list,pred==nil
%string(255)x,y
  n = 1; y = s; toupper(y)
  %while cell##nil %cycle
    x = cell_s; toupper(x)
    %result == list %if x=y
    n = n+1; pred == cell; cell == cell_next
  %repeat
  cell == new(cell); cell_next == nil; cell_s == heapstring(s)
  %result == cell %if pred==nil
  pred_next == cell; %result == list
%end

%string(*)%map group(%integer n)
  %result == nth(admingrouplist,n)
%end

%integerfn addgroup(%string(*)%name s)
%record(stringlist)%name r,p==nil
%integer n=1
  r == admingrouplist
  %while r##nil %cycle
    %result = n %if matches(r_s,s)
    p == r; r == r_next; n = n+1
  %repeat
  r == new(r); r = 0; r_s == heapstring(s)
  %if p==nil %then admingrouplist == r %else p_next == r
  %result = n
%end

%string(*)%map flag(%integer n)
%ownstring(1)null=""
%integer m = 1
  %cycle
    %result == null %if n=0
    %result == nth(adminflaglist,m) %if n&1#0
    n = n>>1; m = m+1
  %repeat
%end

%string(255)%fn flags(%integer n)
%string(255)s=""
  %cycle
    %result = s %if n=0
    s = s."+".flag(n)
    n = (n-1)&n
  %repeat
%end

%integerfn addflag(%string(*)%name s)
%record(stringlist)%name r,p==nil
%integer n=1
  r == adminflaglist
  %while r##nil %cycle
    %result = n %if matches(r_s,s)
    p == r; r == r_next; n = n<<1
  %repeat
  r == new(r); r = 0; r_s == heapstring(s)
  %if p==nil %then adminflaglist == r %else p_next == r
  %result = n
%end

%integerfn pack date(%string(*)%name s)
! Result zero if S does not contain a plausible DD/MM/YY.
! Otherwise (16-bit!) result is  YY<<9 + MM<<5 + DD.
%bytename b == length(s)
%integer d=0,m=0,y=0

  %predicate digit(%integer d,%integername n)
    %falseunless '0'<=d<='9'
    n = n*10-'0'+d; %true
  %end
  
  %result = 0 %unless b=8 %and digit(b[1],d) %and digit(b[2],d)
  %result = 0 %unless b[3]='/' %and digit(b[4],m) %and digit(b[5],m)
  %result = 0 %unless b[3]='/' %and digit(b[7],y) %and digit(b[8],y)
  %result = 0 %unless 1<=d<=31 %and 1<=m<=12 %and y>=80
  %result = y<<9+m<<5+d
%end

%string(8)%fn unpack date(%integer d)
! Re-constitute
%string(8)dmy = "00/00/00"
%bytename b == length(dmy)
%integer m,y
  y = rem(d>>9,100); m = d>>5&15; d = d&31
  b[1] = d//10+'0'; b[2] = rem(d,10)+'0'
  b[4] = m//10+'0'; b[5] = rem(m,10)+'0'
  b[7] = y//10+'0'; b[8] = rem(y,10)+'0'
  %result = dmy
%end

%routine get age(%record(entryfm)%name r)
! Fill in the AGE field in the record.  This is the age of the first
! (youngest!?) file in the directory, as a "last used" estimate.
! Uses FINFO(1).
%string(255)s0,s
%bytename b == length(s)
%integer n,m
  
  %on 3 %start
    %return
  %finish
  
  r_age = 0
  s0 = finfo(r_id,1)
  %while s0 -> s.(" ").s0 %cycle
    r_age = packdate(s) %andexitif s -> ("/")
  %repeat
%end

%routine get sizes(%record(entryfm)%name r)
! Fill in the FILES and BLOCKS fields in the record.
! Parses the result of FINFO(0).
%string(255)s0,s
  
  %on 3 %start
    %return
  %finish

  s0 = finfo(r_id,0)
  %returnunless s0 -> ("(").s0
  r_partition = charno(s,1)-'0' %if s0 -> s.(".").s0
  %returnunless s0 -> (": ").s0
  r_files = stoi(s) %if s0 -> s.(",").s0; 
  %returnunless s0 -> (": ").s0
  %returnunless s0 -> (": ").s0
  r_blocks = stoi(s) %if s0 -> s.("/").s0; 
%end

%integerfn readhalf
! Read a 2-byte number MSB first
%integer a,b
  a = readsymbol; b = readsymbol
  %result = a<<8+b
%end

%integerfn readfull
! Read a 4-byte number MSB first
%integer a,b
  a = readhalf; b = readhalf
  %result = a<<16+b
%end

%routine readstring(%string(*)%name s)
! Read a length-prefixed string
%integer i
  length(s) = readsymbol
  charno(s,i) = readsymbol %for i = 1,1,length(s)
%end

%routine read admin header {assumed opened and selected already}
! We assume the ADMIN database file has been opened and selected.
! Read in the global index array, and also the group and class lists, in
! preparation for reading a single record or the whole rest of the file.
%string(255)s
%integer k,i
  %on 9 %start
    %return
  %finish
  adminindex(i) = 0 %for i = 'A'-1,1,'Z'+2
  adminindex(i) = readfull %for i = 'A'-1,1,'Z'+2
  admingrouplist == nil
  %cycle
    readstring(s); %exitif s=""; i = addgroup(s)
  %repeat
  adminflaglist == nil
  %cycle
    readstring(s); %exitif s=""; i = addflag(s)
  %repeat
%end

%predicate read admin record(%record(entry fm)%name r)
! Seek to the right point in the admin file (assumed opened, index
! assumed read in), and read the relevant record if found.
%integer l1,created,flags,group
%string(255)id,rid,sur,pre
  %on 9 %start
    %false
  %finish
  id = r_id; %falseif id=""; toupper(id)
  l1 = charno(id,1)
  l1 = 'A'-1 %if l1<'A'
  l1 = 'Z'+1 %if l1>'Z'
  %falseif adminindex(l1)>=adminindex(l1+1)
  setinput(adminindex(l1))
  l1 = charno(id,1)
  %cycle
    created = readhalf; flags = readhalf; group = readsymbol
    readstring(rid); readstring(sur); readstring(pre)
    toupper(rid); %exitif rid=id
    %falseif charno(rid,1)>l1
  %repeat
  r_created = created; r_flags = flags; r_group = group
  r_sur == heapstring(sur); r_pre == heapstring(pre); %true
%end
!!end of body

%begin { WHOIS }

%record(entry fm)r = 0

  r_id = cliparam
  toupper(r_id)
  %returnif r_id=""
  openinput(1,adminfile); selectinput(1)
  read admin header
  %unless read admin record(r) %start
    printline("Entry ".r_id." not in database")
    %return
  %finish
  printstring(r_id." (")
  printstring(group(r_group)); printstring(flags(r_flags))
  printline(") ".r_pre." ".r_sur)
  printstring("Created "); printstring(unpackdate(r_created))
  getsizes(r)
  printstring(" in partition "); write(r_partition,0); newline
  write(r_blocks,0); printstring(" blocks in ")
  write(r_files,0); printstring(" files")
  %unless r_files<=0 %start
    getage(r)
    printstring(", last written ")
    printstring(unpackdate(r_age))
  %finish
  newline
%end
