! Various record formats, variables, and routines for the programs ! ADMINHM, ADMINMH, ADMIN, and WHOIS %include "inc:util.imp" %include "inc:wildmat.imp" %externalstring(255)%fnspec finfo(%string(255)d,%integer n) ! The new ADMIN.DB file contains variable-length records (it was the fixed ! size records that made the old ADMIN.DAT so big that it took very long ! to read in or write out). As before, the records are kept sorted by ID, ! and there is an index at the front pointing to the record of the first ! entry beginning with X. The index consists of 29 entries, each a longword ! giving the byte offset from the start of the file of the record it points to. ! The first index entry points to the first record (in case this begins with ! a digit rather than a letter). The second entry points to the first record ! beginning with 'A', the next 25 entries take us to 'Z', the next points to ! the first record beginning with a non-letter (if any), and the last entry ! points off the end of the last record (in fact, to the end of the file). ! The index is followed by the group names, a sequence of length-prefixed ! strings, terminated by a null string. Then come the class (flag) names, ! stored similarly. There must be no more than 255 groups, nor 16 classes. ! There follow the actual records, each stored as follows: ! A 2-byte number giving the creation date of the directory, stored as the ! year (mod 100) times 512, plus 32 times the month, plus the day, i.e. ! 7, 4, and 5 bits for year, month, and day. The MS byte comes first. ! There follows a 2-byte number, MSB first, which represents the set of ! classes in which the directory is a member, each bit represents a class. ! Bit 0 (1) represents the first class which appears in the above list, ! bit 3 (8) the fourth, and so on. There follows a byte for the group ! number, value 1 representing the first. There follow three length-prefixed ! strings for the ID, surname, and prenames respectively. There is NO ! padding byte to make the record length even. %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 findgroup(%string(*)%name s) %record(stringlist)%name r %integer n=1 r == admingrouplist %cycle %result = 0 %if r==nil %result = n %if matches(r_s,s) r == r_next; n = n+1 %repeat %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 findflag(%string(*)%name s) %record(stringlist)%name r %integer n=1 r == adminflaglist %cycle %result = 0 %if r==nil %result = n %if matches(r_s,s) r == r_next; n = n<<1 %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(15)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 %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 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 %record(entry fm)%map read admin file {assumed already opened and selected} ! Read the header and rest, returning a sorted list of records ! in which the ID, PRE, SUR, GROUP, FLAG, CREATED fields are ! filled in, PARTITION, FILES, BLOCKS, and AGE fields are zapped. ! NB the records are ASSUMED to be in the file in sorted order. %string(255)s %record(entryfm)%name list==nil,this,q==nil %on 9 %start dispose(this) %result == list %finish read admin header %cycle this == new(this); this = 0 this_partition = -1 this_created = readhalf this_flags = readhalf this_group = readsymbol readstring(s); toupper(s); this_id <- s readstring(s); this_sur == heapstring(s) readstring(s); this_pre == heapstring(s) this_next == nil %if q==nil %then list == this %else q_next == this; q == this %repeat %end %routine write admin file(%record(entryfm)%name entries) ! Write out the list ENTRIES, with global group and class lists, ! to the file already assumed opened and selected. ! We assume ENTRIES is sorted by ID. %integer offset,limit %record(entryfm)%name this %record(stringlist)%name fg %routine puthalf(%integer k) printsymbol(k>>8&255); printsymbol(k&255) %end %routine putlong(%integer k) puthalf(k>>16); puthalf(k) %end %routine putstring(%string(255)s) %integer i printsymbol(length(s)); printsymbol(charno(s,i)) %for i = 1,1,length(s) %end offset = 29*4 fg == admingrouplist %while fg##nil %cycle offset = offset+length(fg_s)+1; fg == fg_next %repeat offset = offset+1 fg == adminflaglist %while fg##nil %cycle offset = offset+length(fg_s)+1; fg == fg_next %repeat offset = offset+1 putlong(offset); limit = 'A'; this == entries %while this##nil %cycle %while charno(this_id,1)>=limit %cycle putlong(offset) limit = 255 %if limit>'Z' limit = limit+1 %repeat offset = offset+8+length(this_id)+length(this_sur)+length(this_pre) this == this_next %repeat %cycle putlong(offset); %exitif limit>'Z'+1; limit = limit+1 %repeat fg == admingrouplist %while fg##nil %cycle putstring(fg_s); fg == fg_next %repeat putstring("") fg == adminflaglist %while fg##nil %cycle putstring(fg_s); fg == fg_next %repeat putstring("") this == entries %while this##nil %cycle puthalf(this_created); puthalf(this_flags); printsymbol(this_group) putstring(this_id); putstring(this_sur); putstring(this_pre) this == this_next %repeat %end