! B-tree system-dependent module (internal filesystem version). ! Compatibility version: believes in new-style mapped databases and old-style ! unmapped ones. New databases will be created new-style, but the old-style ! code has to remain for the foreseeable future.... ! Note that the tree and data modules are not affected by whether or not ! this layer implements transactions, as they just read and write logical ! blocks using the routines provided here... %externalstring(47) copyright %alias "GDMR_(C)_TREE" = %c "Copyright (C) 1987 George D.M. Ross" %option "-low-nonstandard-nocheck-nodiag-noline" !%option "-low-nonstandard" %constinteger initial size = 4 %constinteger data header site = 1 %include "GDMR_H:FSysAcc.Inc" %include "GDMR_H:FSys.Inc" %constinteger auto truncate flag = 1 !! %include "GDMR_H:Dump.Inc" %include "Moose:Mouse.Inc" %systemroutinespec phex(%integer i) ! Old-style (unmapped) header, contains root block pointer, free list, and some ! stuff used during testing. >>>NB<<< it is essential that the "epoch" field ! of the new-style header is reserved and ZERO in the old-style header. %constinteger old free size = 256 - 8 %recordformat old header fm((%short checksum, %short root, used, flags, free count, %shortarray free(1 : old free size), %short should have, seed) %c %or %bytearray b(0 : 511)) ! New-style mapped database. ! A two-level indirectory is used to convert from logical page number to ! physical page number. The following two records describe the upper and ! lower levels of the map respectively. To save space, the tree root block ! is kept in the master page. ! ! The update strategy for the database is that when a page (data, tree or ! map) is written for the first time a new physical page is allocated for it ! and the map is updated accordingly. The database is commited iff the master ! page has been written out successfully to the alternate site, the newer of the ! two pages being used when the database is first opened. %constinteger map limit = 255 - 1 - 1 - 2 %recordformat master fm(%short checksum, %shortarray map(0 : map limit), %short tree root, %integer epoch) %constinteger page map limit = 254 %recordformat page map fm(%short checksum, %shortarray page(0 : page map limit)) ! The following record holds the state of the open database. If the old header ! field is non-nil then the database is assumed to be an old-style one with ! no indirectory map. Otherwise (for databases with maps) the active master ! page and the most recent lower-level map page are cached, together with a ! used and a modified bitmap giving the status of the physical pages in the ! database: allocations of physical pages take place via the used map, while ! the modified map determines whether a new site is required or not (on first ! write to a page). %constinteger bitmap size = 128 { * 8 to get blocks } %recordformat tree access fm(%record(*)%name access, %integer file token, access mode, file size, %record(old header fm)%name old header, %record(master fm)%name master, %integer master site, %integer master page changed, %record(page map fm)%name page map, %integer last page map site, %integer page map changed, %bytearray physical used(0 : bitmap size), %bytearray logical used(0 : bitmap size), %bytearray modified(0 : bitmap size)) ! Error codes %constinteger checksum error = -500 %constinteger dud mode = -505 ! Checksum stuff %predicate check checksum(%record(*)%name b) ! %short c ! %shortname p ! %integer i ! c = 0; p == shortinteger(addr(b)) ! %for i = 1, 1, 256 %cycle ! c <- c + p; p == p [1] ! %repeat ! %false %if c # 0 ! %true %label L, F A0 = addr(b) D0 = 0; D1 = 255 L: *add.w (A0)+, D0 *dbra D1, L *tst.w D0 *bne F %true F: %false %end %routine set checksum(%record(*)%name b) %short c %shortname p %integer i c = 0; p == shortinteger(addr(b) + 2) %for i = 2, 1, 256 %cycle c <- c + p; p == p [1] %repeat shortinteger(addr(b)) = -c %end ! I/O stuff proper %routine zap block(%record(*)%name block) D0 = 127 L: *clr.l (A0)+ *dbra D0, L %end %integerfn read physical block(%integer access token, block, %record(*)%name buffer) %record(tree access fm)%name a %integer bytes, status a == record(access token) status = fsys read file block(a_access, a_file token, block, bytes, buffer) %result = status %if status # 0 %result = 0 %if check checksum(buffer) %result = checksum error %end %integerfn write physical block(%integer access token, block, %record(*)%name buffer) %record(tree access fm)%name a a == record(access token) set checksum(buffer) %result = fsys write file block(a_access, a_file token, block, 512, buffer) %end %integerfn write unreadable(%record(tree access fm)%name ta, %integer block) %recordformat block fm(%integerarray x(0 : 127)) %record(block fm) b %integer i, status !! printstring("Write unreadable: block "); write(block, 0) !! printstring(", size "); write(ta_file size, 0); newline b_x(i) = 16_ABABABAB %for i = 0, 1, 127 status = fsys write file block(ta_access, ta_file token, block, 512, b) %if status # 0 %start printstring("Write unreadable: "); write(status, 0) newline %result = -10001 %finish ta_file size = block %result = 0 %end ! Map manipulation routines: read and write map pages, obtain and release ! logical and physical pages. !! %routine show bitmap(%bytename map) !! %integer n, i !! %for n = 1, 1, 8 %cycle !! space !! %for i = 0, 1, 7 %cycle !! %if map & (1 << i) = 0 %then print symbol('0') %c !! %else print symbol('1') !! %repeat !! map == map [1] !! %repeat !! %end %integerfn translate for read(%record(tree access fm)%name ta, %integer logical, %integername physical) ! Performs a map lookup to convert the logical block address to a ! physical address. Returns zero physical address if the page is unmapped. %integer map page, map offset, status !! printstring("Translate for read: "); write(logical, 0); newline map page = logical // page map limit map offset = logical - map page * page map limit map page = ta_master_map(map page) %if ta_last page map site # map page %start %if ta_page map changed # 0 %start !! printstring("Previous map page at ") !! write(ta_last page map site, 0) !! printstring(" changed, flushing") !! newline status = write physical block(addr(ta), ta_last page map site, ta_page map) %result = status %if status # 0 %finish ta_page map changed = 0 %if map page = 0 %start !! printstring("New page, zapping"); newline zap block(ta_page map) %else ta_last page map site = -1 !! printstring("Reading from "); write(map page, 0) !! newline status = read physical block(addr(ta), map page, ta_page map) %result = status %if status # 0 %finish ta_last page map site = map page %finish physical = ta_page map_page(map offset) !! printstring("-> "); write(physical, 0); newline %result = 0 %end %integerfn new physical page(%record(tree access fm)%name ta) %integer bit, byte, mask, block, status !! printstring("Map:"); show bitmap(ta_physical used(0)); newline %for byte = 0, 1, bitmap size %cycle %if ta_physical used(byte) # 255 %start %for bit = 0, 1, 7 %cycle mask = 1 << bit %if ta_physical used(byte) & mask = 0 %start ta_physical used(byte) = ta_physical used(byte) ! mask block = byte << 3 + bit !! printstring("New physical page ") !! write(block, 0); printstring(", size ") !! write(ta_file size, 0); newline %if block >= ta_file size %start status = write unreadable(ta, block) %result = -10002 %if status # 0 %finish %result = block %finish %repeat %finish %repeat !! printstring("No new physical page"); newline %result = -10003 %end %integerfn translate for write(%record(tree access fm)%name ta, %integer logical, %integername physical) ! Perform a map lookup. Allocate a new physical page if this is the first ! time the logical page has been modified. %integer bit, byte, status, map page, map offset, new physical map page ! First pull in the map page and translate the current physical ! address. We'll have to check whether or not it has been updated this ! time round. !! printstring("Translate for write: "); write(logical, 0); newline status = translate for read(ta, logical, physical) %result = status %if status # 0 bit = physical & 7; byte = physical >> 3 %if byte > bitmap size %start printstring("Translate for write -- ""modified"" bitmap size error: ") write(byte, 0); newline %finish %if (byte # 0 %or bit # 0) %and ta_modified(byte) & (1 << bit) # 0 %start ! Already updated !! printstring("Already updated to "); write(physical, 0); newline %result = 0 %finish ! Since we know now that this is a first-time update for this ! logical page, we'll have to allocate a new physical page and update ! the corresponding map page. physical = new physical page(ta) !! printstring("First update, mapping to "); write(physical, 0); newline %result = -10004 %if physical <= 0 map page = logical // page map limit map offset = logical - map page * page map limit ! The map page is in store. Update it. If necessary obtain a new ! physical page for it. ta_page map_page(map offset) = physical ta_page map changed = 1 !! printstring("MM"); show bitmap(ta_modified(0)); newline bit = ta_last page map site & 7; byte = ta_last page map site >> 3 %if byte > bitmap size %start printstring("Translate for write -- ""modified"" bitmap size error: ") write(byte, 0); newline %finish %if bit = 0 = byte %or ta_modified(byte) & (1 << bit) = 0 %start ! First modification for map page. Get a new physical page. new physical map page = new physical page(ta) !! printstring("New map page: "); write(new physical map page, 0) !! newline %result = -10005 %if new physical map page < 0 ta_master_map(map page) = new physical map page ta_last page map site = new physical map page ta_master page changed = 1 bit = new physical map page & 7; byte = new physical map page >> 3 %if byte > bitmap size %start printstring("Translate for write -- ""modified"" bitmap size error: ") write(byte, 0); newline %finish ta_modified(byte) = ta_modified(byte) ! (1 << bit) %finish ! Now mark the new physical page as changed bit = physical & 7; byte = physical >> 3 %if byte > bitmap size %start printstring("Translate for write -- ""modified"" bitmap size error: ") write(byte, 0); newline %finish ta_modified(byte) = ta_modified(byte) ! (1 << bit) %result = 0 %end ! Logical block I/O. For unmapped trees these just call the corresponding ! physical I/O routines. Mapped trees require an indirection. %integerfn read block(%integer access token, block, %record(*)%name buffer) %record(tree access fm)%name ta %integer status, physical ta == record(access token) %result = read physical block(access token, block, buffer) %c %if ta_old header ## nil !! printstring("Read (mapped) block "); write(block, 0); newline status = translate for read(ta, block, physical) %result = status %if status # 0 !! printstring("Translated to "); write(physical, 0); newline zap block(buffer) %and %result = 0 %if physical <= 0; ! Unmapped %result = read physical block(access token, physical, buffer) %end %integerfn write block(%integer access token, block, %record(*)%name buffer) %record(tree access fm)%name ta %integer status, physical ta == record(access token) %result = dud mode %if ta_access mode = 0 %result = write physical block(access token, block, buffer) %c %if ta_old header ## nil !! printstring("Write (mapped) block "); write(block, 0); newline status = translate for write(ta, block, physical) %result = status %if status # 0 !! printstring("Translated to "); write(physical, 0); newline %result = write physical block(access token, physical, buffer) %end ! Free block list management. For non-mapped databases the free list is held ! in the header block, while the bitmap and map pages themselves are used for ! mapped databases. %integerfn get new block(%integer access token) %record(tree access fm)%name ta %record(old header fm)%name old header %integer it, i, bit, byte, mask ta == record(access token) %result = dud mode %if ta_access mode = 0 %if ta_old header ## nil %start old header == ta_old header %if old header_free count = 0 %start ! Nothing spare. Should extend. old header_used = 1 %if old header_used = 0; ! Skip data header old header_used = old header_used + 1 i = write block(access token, 0, old header) %result = i %if i < 0 %result = old header_used %else it = old header_free(1); ! Use the first old header_free count = old header_free count - 1 %if old header_free count > 0 %start ! More than one free. Shuffle down the rest. old header_free(i) = old header_free(i + 1) %c %for i = 1, 1, old header_free count %finish i = write block(access token, 0, old header) %result = i %if i < 0 %result = it %finish %finish ! New-style header.... %for byte = 0, 1, bitmap size %cycle %if ta_logical used(byte) # 255 %start %for bit = 0, 1, 7 %cycle mask = 1 << bit %if ta_logical used(byte) & mask = 0 %start ta_logical used(byte) = ta_logical used(byte) ! mask !! printstring("Logical block ") !! write(byte << 3 + bit, 0) !! printstring(" allocated"); newline %result = byte << 3 + bit %finish %repeat %finish %repeat %result = -10006 %end %integerfn free block(%integer access token, which) %record(tree access fm)%name ta %record(old header fm)%name old header %integer i, pos, bit, byte, status, physical %integer map page slot, map page, map offset, new physical map page ta == record(access token) %result = dud mode %if ta_access mode = 0 %if ta_old header ## nil %start old header == ta_old header %result = 0 %if old header_free count >= old free size; ! Throw it away meantime %if old header_free count = 0 %start old header_free(1) = which old header_free count = 1 %else %for pos = 1, 1, old header_free count %cycle %if old header_free(pos) > which %start ! Found insertion position old header_free(i + 1) = old header_free(i) %c %for i = old header_free count, -1, pos old header_free(pos) = which old header_free count = old header_free count + 1 -> write it %finish %repeat ! Muat be last of all old header_free count = old header_free count + 1 old header_free(old header_free count) = which %finish write it: %result = write block(access token, 0, old header) %finish ! New-style header.... !! printstring("Freeing logical block "); write(which, 0); newline bit = which & 7; byte = which >> 3 %if byte > bitmap size %start printstring("Free block bitmap error: "); write(byte, 0) newline %finish ta_logical used(byte) = ta_logical used(byte) & (\ (1 << bit)) ! As well as marking the logical block free we have to zap its entry ! in the translation map and note it as unmodified. map page slot = which // page map limit map offset = which - map page slot * page map limit map page = ta_master_map(map page slot) %result = -10014 %if map page = 0; ! Unmapped as yet?? %if ta_last page map site # map page %start %if ta_page map changed # 0 %start !! printstring("Previous map page at ") !! write(ta_last page map site, 0) !! printstring(" changed, flushing") !! newline status = write physical block(addr(ta), ta_last page map site, ta_page map) %result = status %if status # 0 %finish ta_page map changed = 0 ta_last page map site = -1 !! printstring("Reading from "); write(map page, 0) !! newline status = read physical block(addr(ta), map page, ta_page map) %result = status %if status # 0 ta_last page map site = map page %finish physical = ta_page map_page(map offset) !! printstring("-> "); write(physical, 0); newline ta_page map_page(map offset) = 0 ta_page map changed = 1 ! Now, have we modified this map page before? If not, we'll have ! to allocate a new physical block for it and update the master.... !! printstring("MM"); show bitmap(ta_modified(0)); newline bit = map page & 7; byte = map page >> 3 %if byte > bitmap size %start printstring("Free block -- ""modified"" bitmap size error: ") write(byte, 0); newline %finish %if ta_modified(byte) & (1 << bit) = 0 %start ! First modification for map page. Get a new physical page. new physical map page = new physical page(ta) !! printstring("New map page: "); write(new physical map page, 0) !! newline %result = -10015 %if new physical map page < 0 ta_master_map(map page slot) = new physical map page ta_last page map site = new physical map page ta_master page changed = 1 bit = new physical map page & 7; byte = new physical map page >> 3 %if byte > bitmap size %start printstring("Free block -- ""modified"" bitmap size error: ") write(byte, 0); newline %finish ta_modified(byte) = ta_modified(byte) ! (1 << bit) %finish ! Now mark the new physical page as free and unchanged bit = physical & 7; byte = physical >> 3 %if byte > bitmap size %start printstring("Translate for write -- ""modified"" bitmap size error: ") write(byte, 0); newline %finish ta_modified(byte) = ta_modified(byte) & (\ (1 << bit)) %result = 0 %end ! Tree root determination. Rather than take up an entire block in a known ! location to store the pointer to the tree's root, we decide instead to keep ! it in the database indirectory master page. ! Non-mapped databases hold it in the tree header. %integerfn find root(%integer access token) %record(tree access fm)%name ta ta == record(access token) %result = ta_old header_root %if ta_old header ## nil %result = ta_master_tree root %end %integerfn set root(%integer access token, new root) %record(tree access fm)%name ta %record(old header fm)%name old header ta == record(access token) %result = dud mode %if ta_access mode = 0 %if ta_old header ## nil %start old header == ta_old header old header_root = new root %result = write block(access token, 0, old header) %finish ta_master_tree root = new root ta_master page changed = 1 %result = 0 %end ! Bitmap construction %routine bitmap set(%integer bit, %bytename map) %integer byte, offset byte = bit >> 3; offset = bit & 7 map [byte] = map [byte] ! (1 << offset) %end %integerfn build bitmaps(%record(tree access fm)%name ta) %integer status, map page, map offset, map physical, map logical ta_physical used(0) = 2_00000011; ! Two master pages ta_logical used(0) = 2_00000011; ! Reserved + data header ta_modified(0) = 2_00000011; ! Two master pages (??) %for map page = 0, 1, map limit %cycle map physical = ta_master_map(map page) %if map physical # 0 %start ! Map page exists, so note it and scan it !! printstring("Note map page "); write(map physical, 0) !! newline bitmap set(map physical, ta_physical used(0)) status = read physical block(addr(ta), map physical, ta_page map) %result = status %if status # 0 ta_last page map site = map physical %for map offset = 0, 1, page map limit %cycle map physical = ta_page map_page(map offset) %if map physical # 0 %start ! Database page exists. Note physical site, then ! calculate and note logical address. !! printstring("Note physical database page ") !! write(map physical, 0); newline bitmap set(map physical, ta_physical used(0)) map logical = map page * page map limit + map offset !! printstring("Note logical database page ") !! write(map logical, 0); newline bitmap set(map logical, ta_logical used(0)) %finish %repeat %finish %repeat !! printstring("PU"); show bitmap(ta_physical used(0)); newline !! printstring("LU"); show bitmap(ta_logical used(0)); newline !! printstring("M "); show bitmap(ta_modified (0)); newline %result = 0 %end ! Tree module proper %include "GDMR_H:Tree.Core" %include "GDMR_H:Data.Core" ! open/close/create %owninteger open CPU = 0, open real = 0 %externalintegerfn B tree open by ID(%record(fsys access fm)%name access, %integer request flags, %integer ID, mode, %integername token, flags) %record(tree access fm)%name a == nil %record(master fm)%name other master %integer status, x, access mode, compatible mode !! printstring("Open "); phex(ID) !! printstring(", mode "); write(mode, 0); newline !! open CPU = CPU time; open real = real time %if mode = 0 %start access mode = read access compatible mode = read access %else access mode = read access ! modify access compatible mode = 0 %finish a == new(a); a = 0 !! printstring("'a' at "); phex(addr(a)) !! printstring(", heap "); phex(integer(addr(a) - 4)); newline a_access == access; a_access mode = mode status = fsys open file(access, ID, access mode, compatible mode, request flags, a_file token, a_file size, flags) !! printstring("Open status: "); write(status, 0); newline dispose(a) %and %result = status %if status # 0 a_file size = a_file size >> 9; ! bytes -> blocks a_master == new(a_master) !! printstring("'a_master' at "); phex(addr(a_master)) !! printstring(", heap "); phex(integer(addr(a_master) - 4)); newline status = read physical block(addr(a), 0, a_master) %if status # 0 %start !! printstring("Read pyhysical status: "); write(status, 0); newline x = fsys close file(nil, a_file token, 0) !! printstring("Close status: "); write(x, 0); newline dispose(a_master) dispose(a) !! printstring("Returning "); write(status, 0); newline %result = status %finish %if a_master_epoch = 0 %start !! printstring("Unmapped tree "); phex(ID); newline a_old header == record(addr(a_master)) a_master == nil %else other master == new(other master) !! printstring("'other master' at "); phex(addr(other master)) !! printstring(", heap "); phex(integer(addr(other master) - 4)); newline !! printstring("B-tree "); phex(ID) !! printstring(": master 0 epoch "); phex(a_master_epoch) !! newline status = read physical block(addr(a), 1, other master) %result = status %if status # 0 !! spaces(17); printstring("master 1 epoch ") !! phex(other master_epoch); newline !! dump(512, byteinteger(addr(a_master))) !! dump(512, byteinteger(addr(other master))) %if other master_epoch > a_master_epoch %start dispose(a_master) a_master == other master a_master site = 0; ! Always the *target* site %else dispose(other master) a_master site = 1; ! Always the *target* site %finish a_old header == nil a_page map == new(a_page map); a_page map = 0 !! printstring("'a_page map' at "); phex(addr(a_page map)) !! printstring(", heap "); phex(integer(addr(a_page map) - 4)); newline %if a_access mode # 0 %start status = build bitmaps(a) %result = status %if status # 0 %finish %finish token = addr(a) !! printstring("Opened as "); phex(token) !! printstring(", size is "); write(a_file size, 0); newline %result = 0 %end %externalintegerfn B tree close(%integer token, abandon) %record(tree access fm)%name a %integer status, close CPU, close real a == record(token) %if a_old header ## nil %start dispose(a_old header) %else ! Write out the maps, then dispose them !! printstring("Closing: abandon "); write(abandon, 0) !! printstring(", mpc "); write(a_master page changed, 0) !! printstring(", ms "); write(a_master site, 0) !! printstring(", pmc "); write(a_page map changed, 0) !! printstring(", lpms "); write(a_last page map site, 0) !! newline %if a_access mode # 0 %and abandon = 0 %start %if a_master page changed # 0 %start %if a_page map changed # 0 %start !! printstring("Writing last modified map to ") !! write(a_last page map site, 0); newline status = write physical block(token, a_last page map site, a_page map) %result = status %if status # 0 %finish a_master_epoch = a_master_epoch + 1 !! printstring("Writing master page to ") !! write(a_master site, 0); newline status = write physical block(token, a_master site, a_master) %result = status %if status # 0 %finish %finish dispose(a_page map) dispose(a_master) %finish status = fsys close file(a_access, a_file token, auto truncate flag) !! %if status < 0 %start !! printstring("Close tree failed: "); write(status, 0) !! newline !! %finish dispose(a) !! close CPU = CPU time; close real = real time !! printstring("Tree: "); write(close CPU - open CPU, 0) !! printstring(" CPU, "); write(close real - open real, 0) !! printstring(" real"); newline %result = 0 %end %externalintegerfn B tree create(%record(fsys access fm)%name access, %integer request flags, %string(31) name, %integer partition, %integer benefactor ID, %integername ID) %record(master fm) master %integer status, token, size, flags status = fsys create file(access, name, partition, benefactor ID, request flags, initial size, ID) %result = status %if status # 0 status = fsys open file(nil, ID, modify mode, 0, request flags, token, size, flags) %result = status %if status # 0 ! Initialise the two master pages with epoch 1 & everything else ! zero -- this means that the database is implicitly zero. master = 0 master_epoch = 1 master_checksum = -1 status = fsys write file block(access, token, 0, 512, master) %result = status %if status # 0 status = fsys write file block(access, token, 1, 512, master) %result = status %if status # 0 %result = fsys close file(access, token, 0) %end %end %of %file