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