diff --git a/Bptree.pas b/Bptree.pas new file mode 100755 index 0000000..0a55943 --- /dev/null +++ b/Bptree.pas @@ -0,0 +1,869 @@ +unit BPTree; + +interface + +uses UbixFS, strings; + +const B_NODE_LEAF = $4641454C; +const B_NODE_INDEX = $58444E49; +const B_NODE_OPEN = $4E45504F; + +const B_MAX_NAME_LENGTH = 254; + +type + PbNode = ^TbNode; + PbNodeData = ^TbNodeData; + + uPtr = packed record + case integer of + 0: (iAddr : inodeAddr); + 1: (bPtr : PbNode); + 2: (offset : int64); +{ 3: (iPtr : PubixfsInode);} + end; + + TbNode = packed record + magic : uInt32; + tag : uInt32; + numKeys : uInt32; + size : uInt32; + left : uPtr; + right : uPtr; + parent : uPtr; + reserved : uPtr; + data : array[0..0] of byte; + {data : array[0..64-1] of byte;} + {data : array[0..464-1] of byte;} + {data : array[0..464+512-1] of byte;} + {data : array[0..4048-1] of byte;} + end; + + TbNodeData = packed record + link : uPtr; + key : string[1]; + end; {TbNode} + +type + PbTree = ^bTree; + bTree = object + private + root : uPtr; + curTag : uInt32; + treeHeight : uInt32; + treeLeafKeys : uInt32; + firstDeleted : uPtr; + dataFile : file; + memoryTree : boolean; + f:text; + bNodeSize : uInt32; +{ fs : PUbixFS; + header : PbTreeHeader; + fd : PfileDescriptor; + tag : uInt32;} + function align(keyLength:integer):integer; + function allocEmptyNode:uPtr; + procedure deAllocNode(node:PbNode); + function deleteEntry(u:uPtr; key :PChar; value:uPtr):uPtr; + procedure discardPage(node:PbNode); + function findLeafNode(u:uPtr; key:PChar):uPtr; + procedure freeAll; + procedure freePage(u:uPtr); + function getAddress(node:PbNode):uPtr; + function insertEntry(u:uPtr; key:PChar; value:uPtr):boolean; + function isNull(u:uPtr):boolean; + function loadPage(u:uPtr):PbNode; + procedure savePage(node:PbNode); + procedure setLeft(u:uPtr; newPtr:uPtr); + procedure setRight(u:uPtr; newPtr:uPtr); + procedure setParent(u:uPtr; newPtr:uPtr); + public + constructor init(const filename:string); + function Delete(key:PChar; value:uPtr):uPtr; + function Insert(key:PChar; value:uPtr):boolean; + function Find(key:PChar):uPtr; + procedure Info(u:uPtr); + procedure Print(u:uPtr); + procedure PrintList; + procedure PrintWholeTree; + destructor done; + end; {bTree} + +var nulluPtr:uPtr; + +implementation + +uses strings, dos, crt; + +function compareUPtrs(u1, u2:uPtr):boolean; +begin +{ if (memoryTree) then + result := (u1.bPtr = u2.bPtr) + else} + result := (u1.offset = u2.offset); +end; {compareUPtrs} + +overload = = compareUPtrs; + + +constructor bTree.init; +var node:PbNode; +begin + root := nulluptr; + curTag := 0; + treeHeight := 0; + treeLeafKeys := 0; + bNodeSize := 1024; + firstDeleted := nullUPtr; + + memoryTree := fileName = ''; + + if not (memoryTree) then + begin + + assign(dataFile, filename); + if (fsearch(filename, '')<>'') then + reset(dataFile, bNodeSize) + else + rewrite(dataFile, bNodeSize); + + getmem(node, bNodeSize); + fillchar(node^, bNodeSize, 0); + curTag := fileSize(dataFile); + if (filesize(dataFile) <> 0) then + begin + blockread(dataFile, node^, 1); + while not (isNull(node^.parent)) do + begin + seek(dataFile, dword(node^.parent.offset)); + blockread(dataFile, node^, 1); + end; + root := getAddress(node); + end; + freemem(node, bNodeSize); + end; + assign(f, 'output.txt'); + {assign(f, '');} + rewrite(f); +end; {bTree.init} + +function bTree.align; +begin + result := ((sizeof(TbNodeData) + keyLength + 7) shr 3) shl 3; +end; + +function bTree.allocEmptyNode; +var newNode:PbNode; +begin + result := nulluptr; + if (not isNull(firstDeleted)) then + begin + newNode := loadPage(firstDeleted); + if (newNode = NIL) then exit; + firstDeleted := newNode^.parent; + end + else + begin + getmem(newNode, bNodeSize); + fillchar(newNode^, bNodeSize, 0); + newNode^.tag := curTag; + inc(curTag); + end; + with newNode^ do + begin + magic := B_NODE_LEAF; + numKeys := 1; + size := dword(@newNode^.data) - dword(newNode); + left := nulluPtr; + right := nulluptr; + parent := nulluptr; + reserved := nulluptr; + end; {with} + result := getAddress(newNode); + savePage(newNode); + discardPage(newNode); + +end; {bTree.allocEmptyNode} + +procedure bTree.deAllocNode; +begin + if (node = NIL) then exit; + with node^ do + begin + magic := B_NODE_OPEN; + left := nulluPtr; + right := nulluPtr; + parent := firstDeleted; + reserved := nulluPtr; + if (numKeys <> 0) then writeln('Warning! Node isn''t completely empty!'); + {numKeys := 1; no keys, but get it ready for the next use} + size := dword(@node^.data) - dword(node); + fillchar(data, bNodeSize - size, 0); + end; + firstDeleted := getAddress(node); +end; {btree.deAllocNode} + +function bTree.deleteEntry; +var + bnode : PbNode; + dataNode, nextDataNode:PbNodeData; + nodeSize:uInt32; + curNode, res: integer; + found: boolean; +begin + result := nulluPtr; + + if (isNull(u)) then exit; + bnode := loadPage(u); + + if (bnode^.numKeys = 0) then halt; {this should never happen} + + if (bnode^.magic = B_NODE_LEAF) then + begin + + dataNode := @bnode^.data; + curNode := 0; + repeat + res := strcomp(@dataNode^.key[1], key); + if (res >= 0) then break; + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + inc(curNode); + until (curNode = bnode^.numKeys); + + if (res <> 0) then exit; + + result := dataNode^.link; + + nodeSize := align(length(dataNode^.key)); + nextDataNode := dataNode; + inc(uInt8ArrayPtr(nextDataNode), nodeSize); + move(nextDataNode^, dataNode^, bnode^.size - (dword(dataNode) - dword(bnode) - nodeSize)); + dec(bnode^.numKeys); + dec(bnode^.size, nodeSize); + dataNode := pointer(dword(bnode) + bnode^.size); + fillchar(dataNode^, nodeSize, 0); + dec(treeLeafKeys); + + if (bnode^.numKeys = 0) then + begin + savePage(bnode); + + if not(isNull(bnode^.left)) then setRight(bnode^.left, bnode^.right); + {bnode^.left.bPtr^.right.bPtr := bnode^.right.bPtr;} + if not(isNull(bnode^.right)) then setLeft(bnode^.right, bnode^.left); + {bnode^.right.bPtr^.left.bPtr := bnode^.left.bPtr;} + + if (getAddress(bnode) = root) then + root := nulluptr + else + deleteEntry(bnode^.parent, NIL, getAddress(bnode)); + + deAllocNode(bnode); + end; + end + else {not a leaf} + begin + + dataNode := @bnode^.data; + found := FALSE; + curNode := 0; + for curNode := 0 to bnode^.numKeys do + begin + if (curNode <> bnode^.numKeys) then nodeSize := align(length(dataNode^.key)); + found := (dataNode^.link.bPtr = value.bPtr); + if (found) then break; + inc(uInt8ArrayPtr(dataNode), nodeSize); + end; + + if (not found) then + begin + discardPage(bnode); + exit; + end; + + nextDataNode := dataNode; + inc(uInt8ArrayPtr(nextDataNode), nodeSize); + + if (curNode = bnode^.numKeys) then nextDataNode^.link := dataNode^.link; + + fillchar(dataNode^, nodeSize, 0); + move(nextDataNode^, dataNode^, bnode^.size - (dword(dataNode) - dword(bnode) - nodeSize)); + + dec(bnode^.numKeys); + dec(bnode^.size, nodeSize); + + if (bnode^.numKeys = 0) then + begin + (* + * technically we don't have to set left/right pointers in an index node + *) + if not(isNull(bnode^.left)) then setRight(bnode^.left, bnode^.right); + {bnode^.left.bPtr^.right.bPtr := bnode^.right.bPtr;} + if not(isNull(bnode^.right)) then setLeft(bnode^.right, bnode^.left); + {orig: bnode^.right.bPtr^.left.bPtr := bnode^.left.bPtr;} + + savePage(bnode); + if (getAddress(bnode) = root) then + begin + dataNode := @bnode^.data; + root := dataNode^.link; + dec(treeHeight); + end + else {not the root} + begin + deleteEntry(bnode^.parent, NIL, getAddress(bnode)); + end; + deAllocNode(bnode); + end; {if bnode^.numKeys = 0} + end; + savePage(bnode); + discardPage(bnode); +end; {bTree.deleteEntry} + +procedure bTree.discardPage; +begin + if not (memoryTree) then if (node <> NIL) then freemem(node, bNodeSize); +end; {bTree.discardPage} + +function bTree.insertEntry; +var + midKey:array[byte] of char; + dataNode, nextDataNode:PbNodeData; + nodeSize:uInt32; + keyLength:integer; + curNode : integer; + dataSize:integer; + tmpUPtr:uPtr; + bnode, newNode, rootNode:PbNode; + +begin +{ writeln(f, 'Inserting: ', key);} + result := FALSE; + keyLength := strlen(key); + // keep in mind that we use length + string + null, so the actual maxlength is 254 instead of 255 + if (keyLength = 0) or (keyLength > B_MAX_NAME_LENGTH) then exit; + + nodeSize := align(keyLength); + +{ writeln('nodeSize: ', nodeSize); + nodeSize := sizeof(TbNodeData) + keyLength; + inc(nodeSize, 8 - (nodeSize and not -8));} + + if (isNull(root)) then + begin + + (* + * This case should only occur once when a directory is first created. + *) + root := allocEmptyNode; + if (isNull(root)) then halt; + + rootNode := loadPage(root); + + inc(treeLeafKeys); + inc(treeHeight); + rootNode^.magic := B_NODE_LEAF; + dataNode := @rootNode^.data; + + fillchar(dataNode^, nodeSize, 0); + + dataNode^.link := value; + dataNode^.key[0] := char(keyLength); + move(key^, dataNode^.key[1], keyLength); + + inc(rootNode^.size, nodeSize); + + savePage(rootNode); + discardPage(rootNode); + result := TRUE; + exit + end; + + bnode := loadPage(u); + + if (bnode^.size + nodeSize <= bNodeSize) then + begin + + dataNode := @bnode^.data; + + curNode := 0; + repeat + if (strcomp(key, @dataNode^.key[1]) <= 0) then break; + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + inc(curNode); + until curNode = bnode^.numKeys; + + nextDataNode := dataNode; + inc(uInt8ArrayPtr(nextDataNode), nodeSize); {cast to byte size records} + + move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bNode))); + + fillchar(dataNode^, nodeSize, 0); + + dataNode^.key[0] := char(keyLength); + move(key^, dataNode^.key[1], keyLength); + + dataNode^.link := value; + + if (bnode^.magic = B_NODE_INDEX) then + begin + if (not isNull(dataNode^.link)) then setParent(dataNode^.link, getAddress(bnode)); + {if (dataNode^.link.bPtr <> NIL) then + dataNode^.link.bPtr^.parent.bPtr := bnode;} + + tmpUPtr := dataNode^.link; + dataNode^.link := nextDataNode^.link; + nextDataNode^.link := tmpUPtr; + end; + inc(bnode^.numKeys); + + if (bnode^.magic = B_NODE_LEAF) then inc(treeLeafKeys); + inc(bNode^.size, nodeSize); + savePage(bnode); + end + else + begin + + newNode := loadPage(allocEmptyNode); + if (newNode = NIL) then halt; + + newNode^.magic := bnode^.magic; + + newNode^.left := getAddress(bnode); + newNode^.right := bnode^.right; + newNode^.parent := bnode^.parent; + + if not(isNull(newNode^.right)) then setLeft(newNode^.right, getAddress(newNode)); + bnode^.right := getAddress(newNode); + + dataNode := @bnode^.data; + + for curNode:=0 to (bnode^.numKeys div 2)-1 do + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + + fillchar(midKey, sizeof(midKey), 0); + move(dataNode^.key[1], midKey, length(dataNode^.key)); + + if (bnode^.magic = B_NODE_LEAF) then + begin + + { newNode^.left := getAddress(bnode); + newNode^.right := bnode^.right; + if not(isNull(newNode^.right)) then setLeft(newNode^.right, getAddress(newNode)); + bnode^.right := getAddress(newNode);} + + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + + dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); + move(dataNode^, newNode^.data, dataSize); + fillchar(dataNode^, dataSize, 0); + + dec(bnode^.size, dataSize); + newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); + + newNode^.numKeys := bnode^.numKeys - (bnode^.numKeys div 2)-1; + + dec(bnode^.numKeys, newNode^.numKeys); + + savePage(bnode); + savePage(newNode); + if (strcomp(key, midKey) <= 0) then + insertEntry(getAddress(bnode), key, value) + else + insertEntry(getAddress(newNode), key, value); + + end + else {not a leaf} + begin + + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + + dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); + move(dataNode^, newNode^.data, dataSize); + + fillchar(dataNode^, dataSize, 0); + + dec(bnode^.size, dataSize); + newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); + newNode^.numKeys := bnode^.numKeys - (bnode^.numKeys div 2)-1; + dec(bnode^.numKeys, newNode^.numKeys); + dec(bnode^.numKeys); + + savePage(newNode); + + dataNode := @newNode^.data; + for curNode := 0 to newNode^.numKeys do + begin + setParent(dataNode^.link, getAddress(newNode)); + {dataNode^.link.bPtr^.parent.bPtr := newNode;} + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + end; + savePage(bnode); + + if (strcomp(key, midKey) <= 0) then + insertEntry(getAddress(bnode), key, value) + else + insertEntry(getAddress(newNode), key, value); + + end; + + (* + * If we were the root node, then create a new one + *) + + if (getAddress(bnode) = root) then + begin + + root := allocEmptyNode; + rootNode := loadPage(root); + setParent(getAddress(bnode), root); + setParent(getAddress(newNode), root); +{ bnode^.parent := root; + newNode^.parent := root;} + inc(treeHeight); + {dataNode := @newNode^.data; } + dataNode := @rootNode^.data; + with rootNode^ do + begin + magic := B_NODE_INDEX; + {numKeys := 1;} + dataNode^.link := getAddress(bnode); + keyLength := strlen(midKey); + + dataNode^.key[0] := char(keyLength); + move(midKey, dataNode^.key[1], keyLength); + + inc(size, align(keyLength)); + inc(size, sizeof(uPtr)); + inc(uInt8ArrayPtr(dataNode), align(keyLength)); + dataNode^.link := getAddress(newNode); + + end; {with} + savePage(rootNode); + discardPage(rootNode); + end {if oldNode = root} + else + begin + insertEntry(bnode^.parent, midKey, getAddress(newNode)); + end; + discardPage(newNode); + end; {if out of space in this node} + discardPage(bnode); + result := TRUE; +end; {bTree.insertEntry} + +function bTree.isNull; +begin + if (memoryTree) then + result := (u.bPtr = nulluPtr.bPtr) + else + result := (u.offset = nulluPtr.offset) +end; {bTree.isNull} + +function bTree.loadPage; +var node:PbNode; +begin + result := NIL; + if isNull(u) then exit; + + if (memoryTree) then + node := u.bPtr + else + begin + seek(dataFile, dword(u.offset)); + getmem(node, bNodeSize); + blockread(dataFile, node^, 1); + end; + + result := node +end; {bTree.loadPage} + +procedure bTree.savePage; +begin + if (memoryTree) then exit; + + if (node = NIL) then exit; + seek(dataFile, dword(node^.tag)); + blockwrite(dataFile, node^, 1); + +end; {btree.savePage} + +procedure bTree.setLeft; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.left := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setLeft} + +procedure bTree.setParent; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.parent := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setParent} + +procedure bTree.setRight; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.right := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setRight} + +procedure bTree.findLeafNode; +var + bNode:PbNode; + dataNode:PbNodeData; + i:integer; + link:uPtr; +begin + result := nullUPtr; + + if (isNull(u)) then exit; + + bnode := loadPage(u); + + if (bnode^.magic = B_NODE_LEAF) then + begin + result := u; + discardPage(bnode); + exit; + end + else + begin + dataNode := @bnode^.data; + for i:=0 to bnode^.numKeys -1 do + begin + if (strcomp(key, @dataNode^.key[1]) <= 0) then + begin + link := dataNode^.link; + discardPage(bnode); + result := findLeafNode(link, key); + exit; + end; + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + end; {for} + link := dataNode^.link; + discardPage(bnode); + result := findLeafNode(link, key); + end; + +end; {bTree.findLeafNode} + +procedure bTree.freeAll; +begin + if not(memoryTree) then exit; + freePage(root); + root := nulluptr; +end; {bTree.freeAll} + +procedure bTree.freePage; +var + curNode:integer; + dataNode:PbNodeData; + node : PbNode; +begin + + if isNull(u) then exit; + + node := u.bPtr; + curNode := 0; + if (node^.magic = B_NODE_INDEX) then + begin + dataNode := @node^.data; + for curNode := 0 to node^.numKeys-1 do + begin + freePage(dataNode^.link); + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + end; + freePage(dataNode^.link); + end; + freemem(node, bNodeSize); +end; {bTree.freeAll} + +function bTree.getAddress; +var u:uPtr; +begin + u := nulluptr; + if (memoryTree) then + u.bPtr := node + else + u.offset := node^.tag; { * bNodeSize;} + + result := u; +end; {bTree.getAddress} + +function bTree.Delete; +begin + result := find(key); +{ result := deleteEntry(findLeafNode(root, key), key, value);} +end; {bTree.Delete} + +function bTree.Find; +var + bnode:PbNode; + dataNode:PbNodeData; + i, res:integer; + +begin + result := nulluPtr; + + bnode := loadPage(findLeafNode(root, key)); + if (bnode = NIL) then exit; {oops! serious problem} + + if (bnode^.magic <> B_NODE_LEAF) then writeln('Error! not in leaf nodes'); + + dataNode := @bnode^.data; + + for i:=0 to bnode^.numKeys-1 do + begin + res := strcomp(@dataNode^.key[1], key); + if (res >= 0) then break; + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + end; + if (res = 0) then result := dataNode^.link; + discardPage(bnode); +end; {bTree.Find} + +procedure bTree.Info; +const leafStr:array[boolean] of string[5] = ('INDEX', 'LEAF'); +var + dataNode:PbNodeData; + curNode:integer; + node : PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + write(f, ' ', hex(dword(node^.parent.offset))); + writeln(f, ' ', leafStr[node^.magic = B_NODE_LEAF]); + write(f, hex(dword(node^.left.bPtr)), ' <-> ', hex(dword(node)), ' <-> ', hex(dword(node^.right.bPtr))); + writeln(f, ' | capacity ', node^.size,'(', bNodeSize, ')', ' | keys:', node^.numKeys); + if (node^.numKeys = 0) then exit; + dataNode := @node^.data; + + curNode := 0; + for curNode := 0 to node^.numKeys-(ord(node^.magic = B_NODE_LEAF)) do + begin + + write(f, '| ', curNode:2, '| '); + write(f, '['); + + if (node^.magic = B_NODE_LEAF) then + begin +{ if (dataNode^.link.iPtr <> NIL) then + write(f, dataNode^.link.iPtr^.name) + else + write(f, '/');} + end + else + write(f, hex(longint(dword(dataNode^.link.offset)))); + write(f, ']'); + + if (curNode <> node^.numKeys) then + writeln(f, ' {', dataNode^.key, '} '); + + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + end; + discardPage(node); + writeln(f); +end; {bTree.Info} + +function bTree.Insert; +begin + result := insertEntry(findLeafNode(root, key), key, value); +end; {bTree.Insert} + +procedure bTree.Print; +var + curNode:integer; + dataNode:PbNodeData; + node : PbNode; + link : uPtr; +begin + if isNull(u) then exit; + Info(u); + node := loadPage(u); + curNode := 0; + if (node^.magic = B_NODE_INDEX) then + begin + dataNode := @node^.data; + for curNode := 0 to node^.numKeys-1 do + begin + Print(dataNode^.link); + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + end; + Print(dataNode^.link); + end; + discardPage(node); +end; {bTree.Print} + +procedure bTree.PrintList; +var + dataNode:PbNodeData; + node:PbNode; + link:uPtr; + i : integer; +begin + + if (isNull(root)) then exit; + + node := loadPage(root); + while (node^.magic = B_NODE_INDEX) do + begin + dataNode := @node^.data; + link := dataNode^.link; + discardPage(node); + node := loadPage(link); + end; + + while (not isNull(link)) do + begin + dataNode := @node^.data; + for i := 0 to node^.numKeys -1 do + begin + writeln(f, dataNode^.key); + inc(uInt8ArrayPtr(dataNode), align(length(dataNode^.key))); + end; {for i} + link := node^.right; + discardPage(node); + node := loadPage(link); + end; {while} + +end; {bTree.PrintList} + +procedure bTree.PrintWholeTree; +begin + Print(root); + writeln(f, '---------------------------------------------------------------------------'); +end; {bTree.PrintWholeTree} + +destructor bTree.done; +begin + if not(memoryTree) then close(dataFile); + + if (not isNull(root)) then + begin + writeln('tree height: ', treeHeight); + writeln('tree leaf key count: ', treeLeafKeys); + writeln('Total number of nodes: ', curTag); + freeAll; + close(f); + + end; + +end; {bTree.done} + +begin + {$ifdef MEMORY_TREE} + fillchar(nulluptr, sizeof(nulluptr), 0); + {$else} + nulluptr.offset := -1; + {$endif} +end. \ No newline at end of file diff --git a/DEVICE.PAS b/DEVICE.PAS new file mode 100755 index 0000000..bc1611d --- /dev/null +++ b/DEVICE.PAS @@ -0,0 +1,69 @@ +unit Device; + +interface + +const SECTOR_SIZE = 512; + +type + PDevice = ^TDevice; + + readFunc = function(dev:PDevice; ptr:pointer; offset:int64; length:dword):integer; + writeFunc = function(dev:PDevice; ptr:pointer; offset:int64; length:dword):integer; + resetFunc = function:integer; + initFunc = function(dev:PDevice):integer; + ioctlFunc = procedure; + stopFunc = procedure; + startFunc = procedure; + standbyFunc = procedure; + + TDevice = object + public + major : integer; + info : pointer; + sectors : dword; + constructor init; + function read(ptr:pointer; offset:int64; length:dword):integer; virtual; + function write(ptr:pointer; offset:int64; length:dword):integer; virtual; + destructor done; virtual; + end; // TDevice; + +{ TDevice = packed record + major : integer; + info : pointer; + sectors : dword; + read : readFunc; + write : writeFunc; + reset : resetFunc; + init : initFunc; + ioctl : ioctlFunc; + stop : stopFunc; + standby : standbyFunc; + end; } + +implementation + +constructor TDevice.init; +begin + major := 0; + info := NIL; + sectors := 0; +end; // TDevice.init + +function TDevice.read; +begin + result := -1; +end; // TDevice.read + +function TDevice.write; +begin + result := -1; +end; // TDevice.write + +destructor TDevice.done; +begin + major := 0; + info := NIL; + sectors := 0; +end; // TDevice.done + +end. \ No newline at end of file diff --git a/DISKDRV.PAS b/DISKDRV.PAS new file mode 100755 index 0000000..17c9f43 --- /dev/null +++ b/DISKDRV.PAS @@ -0,0 +1,134 @@ +unit diskDrv; + +interface + +uses device; + +type + PDiskDrive = ^TDiskDrive; + TDiskDrive = object(TDevice) + private + diskDrive:file; + openFile:boolean; + public + constructor init(const filename:string); + function read(ptr:pointer; offset:int64; length:dword):integer; virtual; + function write(ptr:pointer; offset:int64; length:dword):integer; virtual; + destructor done; virtual; + end; // TdiskDrive +//function dev_diskDrive(const filename:string):PDevice; +//function dev_diskDestroy:integer; + +implementation + +uses dos; + +constructor TDiskDrive.init; +begin + inherited init; + openFile := FALSE; + if (filename = '') then exit; + writeln('filename: ', filename); + if (fsearch(filename, '') = '') then exit; + // The file must exist! + assign(diskDrive, filename); + {$I-} reset(diskDrive, 512); {$I+} + if (IOResult() <> 0) then writeln('Could not open file'); //exit; // should yell louder + + major := 1; + sectors := filesize(diskDrive); // returns the number of records + writeln('sectors: ', sectors); + openFile := TRUE; +end; // TDiskDrive.init + +function TDiskDrive.read; +begin + if (offset+length > sectors) then + begin + writeln('offset + length > sectors'); + writeln('offset: ', offset); + writeln('length: ', length); + writeln('sectors: ', sectors); + halt; + end; + seek(diskDrive, dword(offset)); + blockread(diskDrive, ptr^, length); + result := length; +end; // TDiskDrive.read + +function TDiskDrive.write; +begin + if (offset+length > sectors) then + begin + writeln('offset + length > sectors'); + writeln('offset: ', offset); + writeln('length: ', length); + writeln('sectors: ', sectors); + halt; + end; + seek(diskDrive, dword(offset)); + blockwrite(diskDrive, ptr^, length); + result := length; +end; + +destructor TDiskDrive.done; +begin + inherited done; + if (openFile) then close(diskDrive); +end; // TDiskDrive.done +(* +function diskDrive_read(dev:PDevice; ptr:pointer; offset:int64; length:dword):integer; +begin + if (offset+length > dev^.sectors) then + begin + writeln('offset + length > dev^.sectors'); + writeln('offset: ', offset); + writeln('length: ', length); + writeln('dev^.sectors: ', dev^.sectors); + halt; + end; + seek(diskDrive, dword(offset)); + blockread(diskDrive, ptr^, length); + result := length; +end; {diskDrive_read} + +function diskDrive_write(dev:PDevice; ptr:pointer; offset:int64; length:dword):integer; +begin + if (offset+length > dev^.sectors) then + begin + writeln('offset + length > dev^.sectors'); + writeln('offset: ', offset); + writeln('length: ', length); + writeln('dev^.sectors: ', dev^.sectors); + halt; + end; + seek(diskDrive, dword(offset)); + blockwrite(diskDrive, ptr^, length); + result := length; +end; {diskDrive_write} + +function dev_diskDrive; +var diskDevice : PDevice; +begin + assign(diskDrive, filename); + reset(diskDrive, 512); + new(diskDevice); + + with diskDevice^ do + begin + major := 1; + sectors := filesize(diskDrive); + read := diskDrive_read; + write := diskDrive_write; + end; + result := diskDevice; + +end; + +function dev_diskDestroy; +begin + close(diskDrive); +end; +*) +end. + diff --git a/FSABSTRA.PAS b/FSABSTRA.PAS new file mode 100755 index 0000000..d54ce9f --- /dev/null +++ b/FSABSTRA.PAS @@ -0,0 +1,31 @@ +unit FSABSTRACT; + +interface + +uses Device; + +type + vfs_abstract = object + private + prev : ^vfs_abstract; + next : ^vfs_abstract; + dev : PDevice; + public + constructor init; + destructor done; virtual; + end; {vfs_abstract} + +implementation + +constructor vfs_abstract.init; +begin + next := NIL; + prev := NIL; + dev := NIL; +end; + +destructor vfs_abstract.done; +begin +end; + +end. \ No newline at end of file diff --git a/MKUFS.PAS b/MKUFS.PAS new file mode 100755 index 0000000..7d152b0 --- /dev/null +++ b/MKUFS.PAS @@ -0,0 +1,16 @@ +program mkufs; + +const DISKSIZE = 1024*1440; + +var + F : file; + i : integer; + sector:array[0..511] of byte; +begin + assign(f, 'ubixfs.dsk'); + rewrite(f, 512); + fillchar(sector, sizeof(sector), 0); + for i := 0 to (DISKSIZE div 512) -1 do + blockwrite(f, sector, 1); + close(f); +end. \ No newline at end of file diff --git a/OBJIMAGE.PAS b/OBJIMAGE.PAS new file mode 100755 index 0000000..4a243d9 --- /dev/null +++ b/OBJIMAGE.PAS @@ -0,0 +1,230 @@ +(********************************************************************) +(* *) +(* OBJIMAGE.PAS *) +(* *) +(* Object Oriented Graphics Image Support *) +(* *) +(* Copyright (c) 2002 *) +(* Mark J Iuzzolino *) +(* *) +(* *) +(********************************************************************) + +unit objImage; + +interface + +uses ObjGfx40; + +type ogImageType = (NOGRAPHIC,BMP); + +const ogImageTypeStr:array[ogImageType] of string[3]=('','BMP'); +type + ogImagePtr = ^ogImage; + ogImage = object + private + tmpBuf:^ogSurface; + c_buf:pointer; + c_buf_size:uInt32; + c_buf_idx:uInt32; + public + constructor init; + function SaveGfx(var SrcObject:ogSurface; const gfxfile:string;GfxType:ogImageType):boolean; virtual; + function SaveGfxTo(var SrcObject:ogSurface; const gfxfile:string; + GfxType:ogImageType;offset:longint):boolean; virtual; + destructor done; + end; + +type + ogImageFmt = record + pixFmt : ogPixelFmt; + xRes : uInt32; + yRes : uInt32; + end; + +implementation + +uses dos; + +type + BitMapHeaDer = packed record + width,height : uInt16; + left,top : uInt16; + BitPlanes : byte; + Masking : byte; + Compress : byte; + Paddington : byte; + Transparency : word; + XAspectRatio : byte; + YAspectRatio : byte; + PageWidth : word; + PageHeight : word; + end; + +type + WinBMPFileHeader = packed record + FileType : uInt16; + FileSize : uInt32; + Reserved1 : uInt16; {always 0} + Reserved2 : uInt16; {always 0} + BitmapOffset : uInt32; + end; + +type + Win3xBitmapHeader = packed record + FileHeader : WinBMPFileHeader; + Size : uInt32; + Width,Height : Int32; + Planes : uInt16; + BitsPerPixel : uInt16; + { Fields added for Windows 3.x follow this line} + Compression : uInt32; + SizeOfBitmap : uInt32; + HorzResolution : Int32; + VertResolution : Int32; + ColoursUsed : UInt32; + ColoursImportant : UInt32; + end; + +type + Win3xPaletteElement = packed record + blue,green,red,pad : byte; + end; + +function bswapl(input:uInt32):uInt32; assembler; +asm + mov eax,input + bswap eax + mov result, eax +(* + * mov ax,word ptr input+2 + * xchg ah,al + * mov dx,word ptr input + * xchg dh,dl + *) +end; + +function bswapw(input:uInt16):uInt16; assembler; +asm + mov ax,input + xchg ah,al + mov result, ax +end; + +constructor ogImage.Init; +begin + tmpBuf:=NIL; + c_buf:=NIL; + c_buf_size:=0; + c_buf_idx:=0; +end; + +function ogImage.saveGfx; +begin + saveGfx:=saveGfxTo(SrcObject,gfxfile,gfxtype,0); +end; + +function ogImage.saveGfxTo; +const ID_S:array[0..7] of char='MOOISHI!'; +var header:pointer; + ColourUsage:array[byte] of byte; + x,y,index,count,tmp,linesize:word; + cbuf:pointer; + colour:byte; + result:dword; + plte:Win3xPaletteElement; + ID_Chunk:array[0..3] of char; + bm:UInt32; + data:pointer; + f:file; +label SafeExit; +begin + SaveGfxTo:=FALSE; + if not(srcObject.ogAvail) then exit; + bm := (SrcObject.ogGetBPP+7) shr 3; + getmem(header,256); + fillchar(header^,256,0); + assign(f,gfxfile); + if (offset=0) then + rewrite(f,1) + else + begin + if (fsearch(gfxfile,'')='') then exit; + reset(f,1); + seek(f,offset); + end; + + with Win3xBitmapHeader(header^) do + begin +{ fillchar(ColourUsage,sizeof(ColourUsage),0); + + for y:=0 to SrcObject.GetMaxY do + for x:=0 to SrcObject.GetMaxX do + ColourUsage[SrcObject.getpixel(x,y)]:=1; + colour:=0; + + for x:=0 to high(ColourUsage) do + inc(colour,ColourUsage[x]);} + + Size:=sizeof(Win3xBitmapHeader)-sizeof(FileHeader); + width:=SrcObject.ogGetMaxX+1; + linesize:=((width*bm+3) shr 2) shl 2; + Height:=(SrcObject.ogGetMaxY+1); + with FileHeader do + begin + FileType:=$4D42; + BitMapOffset:=sizeof(Win3xBitmapHeader){+sizeof(Win3xPaletteElement)*256}; + FileSize:=BitMapOffset+LineSize*dword(height); + Reserved1:=0; + Reserved2:=0; + end; + Planes:=1; + BitsPerPixel:=SrcObject.ogGetBPP; + Compression:=0; + HorzResolution:=0; + VertResolution:=0; + ColoursUsed:=0; + ColoursImportant:=0; + blockwrite(f,header^,sizeof(Win3xBitMapHeader)); + plte.pad:=0; + plte.red:=0; + plte.green:=0; + plte.blue:=0; + tmp:=linesize-width*bm; + with SrcObject, plte do + case ogGetBPP of + 24:for y:=ogGetMaxY downto 0 do + begin + data:=ogGetPtr(0,y); + blockwrite(f,data^,width*bm); +{ for x:=0 to GetMaxX do + begin + unpackRGB(getPixel(x,y),red, green, blue); + blockwrite(f,blue,1); + blockwrite(f,green,1); + blockwrite(f,red,1); + end; + plte.red:=0; + plte.green:=0; + plte.blue:=0;} + if tmp<>0 then blockwrite(f,plte,tmp) + end; + end; {case } +{ for y:=GetMaxY downto 0 do + begin + blockwrite(f,pointer(dword(Buffer)+LineOfs^[y])^,width); + if tmp<>0 then blockwrite(f,plte,tmp) + end} + end; + SaveGfxTo:=TRUE; +SafeExit: + close(f); + freemem(header,256); +end; + +destructor ogImage.done; +begin +{ if (tmpBuf<>NIL) then dispose(tmpBuf,ogDone);} +end; + +end. \ No newline at end of file diff --git a/RAMDRIVE.PAS b/RAMDRIVE.PAS new file mode 100755 index 0000000..154cc44 --- /dev/null +++ b/RAMDRIVE.PAS @@ -0,0 +1,127 @@ +unit ramdrive; + +interface + +uses device; + +type + PRamDrive = ^TRamDrive; + TRamDrive = object(Tdevice) + private + ramData:pointer; + ramDriveSize:dword; + public + constructor init(ramDriveSectors:dword); + function read(ptr:pointer; offset:int64; length:dword):integer; virtual; + function write(ptr:pointer; offset:int64; length:dword):integer; virtual; + destructor done; virtual; + end; // TRamDrive + +(* +function dev_ramDrive:PDevice; +function dev_ramDestroy:integer; +*) +implementation + +{const RAM_DRIVE_SIZE = 1024*1024*20;} + +constructor TRamDrive.init; +begin + inherited init; + if (ramDriveSectors = 0) then runError; // I can't imagine why you'd want a 0 byte ramdrive + ramDriveSize := ramDriveSectors * SECTOR_SIZE; + ramData := NIL; + GetMem(ramData, ramDriveSize); + if (ramData = NIL) then runError; + major := 1; + sectors := ramDriveSectors; +end; // TRamDrive.init + +function TRamDrive.read; +var data:pointer; +begin + data := pointer(dword(ramData) + dword(offset)*SECTOR_SIZE); + if (offset+length > sectors) then runError; + move(data^, ptr^, length*SECTOR_SIZE); + result := length +end; // TRamDrive.read + +function TRamDrive.write; +var data:pointer; +begin + data := pointer(dword(ramData) + dword(offset) * 512); + if (offset+length > sectors) then halt; + move(ptr^, data^, length*512); + result := length; +end; // TRamDrive.write + +destructor TRamDrive.done; +begin + inherited done; + if (ramData <> NIL) then FreeMem(ramData, ramDriveSize); + ramData := NIL; +end; // TRamDrive.done +(* +var ram_data:pointer; +var ram_drive_size:dword; + +function ramDrive_read(dev:PDevice; ptr:pointer; offset:int64; length:dword):integer; +var data:pointer; +begin + data := pointer(dword(ram_data) + dword(offset)*512); + if (offset+length > dev^.sectors) then halt; + move(data^, ptr^, length*512); + result := length; +end; {ramDrive_read} + +function ramDrive_write(dev:PDevice; ptr:pointer; offset:int64; length:dword):integer; +var data:pointer; +begin + data := pointer(dword(ram_data) + dword(offset) * 512); + if (offset+length > dev^.sectors) then halt; + move(ptr^, data^, length*512); + result := length; +end; {ramDrive_write} + +function dev_ramDrive:PDevice; +var + ramDevice : PDevice; + ramDisk : file; + ramDriveSize : dword; +begin + new(ramDevice); + assign(ramDisk, 'ram.dsk'); + reset(ramDisk, 1); + ram_drive_size := filesize(ramDisk); + + getmem(ram_data, ram_drive_size); + fillchar(ram_data^, ram_drive_size, 0); + blockread(ramDisk, ram_data^, ram_drive_size); + close(ramDisk); + + with ramDevice^ do + begin + major := 1; + sectors := ram_drive_size div 512; + read := ramDrive_read; + write := ramDrive_write; + end; + result := ramDevice; +end; + +function dev_ramDestroy:integer; +var + ramDisk : file; + +begin + assign(ramDisk, 'ram.dsk'); + reset(ramDisk, 1); + blockwrite(ramDisk, ram_data^, ram_drive_size); + close(ramDisk); + freemem(ram_data, ram_drive_size); +end; + +begin + ram_data := NIL; + ram_drive_size := 0; *) +end. \ No newline at end of file diff --git a/ROM8X14.DPF b/ROM8X14.DPF new file mode 100755 index 0000000..376a5ef --- /dev/null +++ b/ROM8X14.DPF Binary files differ diff --git a/ROM8X16.DPF b/ROM8X16.DPF new file mode 100755 index 0000000..dfb28b2 --- /dev/null +++ b/ROM8X16.DPF Binary files differ diff --git a/ROM8X8.DPF b/ROM8X8.DPF new file mode 100755 index 0000000..b65b08d --- /dev/null +++ b/ROM8X8.DPF Binary files differ diff --git a/TESTFS.PAS b/TESTFS.PAS new file mode 100755 index 0000000..23a079b --- /dev/null +++ b/TESTFS.PAS @@ -0,0 +1,195 @@ +uses UbixFS, Device, ramDrive, diskDrv, debug, strings, dos, crt, math; + +var + drive : PRamDrive; + // drive:PDiskDrive; + fs: PUbixFS; + filehandle:int32; + buf:array[0..1023] of integer; + count:integer; + handle2:integer; + +procedure copyToFS(source:string); +var + handle:integer; + f:file; + buf:pointer; + size:integer; + dest:pchar; + destSize:integer; +begin + + assign(f, source); + {$I-} reset(f, 1); {$I+} + if IOREsult <> 0 then exit; + size := filesize(f); + getmem(buf, size); + fillchar(buf^, size, 0); + blockread(f, buf^, size); + destSize := length(source) + 2; + GetMem(dest, destSize); + fillchar(dest^, destSize, 0); + strPCopy(dest, '/'+source); + close(f); + handle := fs^.vfs_fCreate(dest); + if (handle = -1) then writeln('Error creating file in FS'); + fs^.vfs_write(handle, buf, size); + fs^.vfs_close(handle); + freemem(buf, size); + freemem(dest, destSize); +end; + +procedure copyFromFS(source:pchar; dest:string); +var + handle:integer; + f:file; + buf:pointer; + size:integer; +begin + assign(f, dest); + rewrite(f, 1); + blockwrite(f, buf^, size); + close(f); +end; + +procedure test1(const file1, file2:string); +const buf1Size = 1 shl 12; +const buf2Size = 1 shl 13; +var + f1, f2:file; + d1, d2:integer; + dest:pchar; + buf:pointer; + bytesReadIn:integer; + lResult:integer; +begin + assign(f1, file1); + assign(f2, file2); + {$I-} reset(f1, 1); {$I+} + if IOResult <> 0 then exit; + {$I-} reset(f2, 1); {$I+} + if IOResult <> 0 then exit; + getMem(buf, max(buf1Size, buf2Size)); + + getMem(dest, length(file1)+2); + strPCopy(dest, '/'+file1); + d1 := fs^.vfs_fCreate(dest); + if (d1 = -1) then writeln('Error creating file in FS'); + freeMem(dest, length(file1)+2); + getMem(dest, length(file2)+2); + strPCopy(dest, '/'+file2); + d2 := fs^.vfs_fCreate(dest); + freemem(dest, length(file2)+2); + if (d2 = -1) then writeln('Error creating file in FS'); + repeat + bytesReadIn := 0; + blockRead(f1, buf^, buf1Size, lResult); + inc(bytesReadIn, lResult); +writeln(file1, ' -------->'); + fs^.vfs_write(d1, buf, lResult); +writeln('<---------'); + blockRead(f2, buf^, buf2Size, lResult); + inc(bytesReadIn, lResult); +writeln(file2, ' -------->'); + fs^.vfs_write(d2, buf, lResult); +writeln('<---------'); + until bytesReadIn = 0; + fs^.vfs_close(d1); + fs^.vfs_close(d2); + freemem(buf, max(buf1Size, buf2Size)); + close(f1); + close(f2); +end; + +procedure test2(const file1, file2, file3:string); +const bufSize = 1 shl 10; +var + f1, f2, f3:file; + d1, d2, d3:integer; + dest:pchar; + buf:pointer; + bytesReadIn:integer; + lResult:integer; +begin + assign(f1, file1); + assign(f2, file2); + assign(f3, file3); + {$I-} reset(f1, 1); {$I+} + if IOResult <> 0 then exit; + {$I-} reset(f2, 1); {$I+} + if IOResult <> 0 then exit; + {$I-} reset(f3, 1); {$I+} + if IOResult <> 0 then exit; + getMem(buf, bufSize); + getMem(dest, length(file1)+2); + strPCopy(dest, '/'+file1); + d1 := fs^.vfs_fCreate(dest); + if (d1 = -1) then writeln('Error creating file in FS'); + freeMem(dest, length(file1)+2); + getMem(dest, length(file2)+2); + strPCopy(dest, '/'+file2); + d2 := fs^.vfs_fCreate(dest); + freemem(dest, length(file2)+2); + if (d2 = -1) then writeln('Error creating file in FS'); + getMem(dest, length(file3)+2); + strPCopy(dest, '/'+file3); + d3 := fs^.vfs_fCreate(dest); + freemem(dest, length(file3)+2); + if (d3 = -1) then writeln('Error creating file in FS'); + + repeat + bytesReadIn := 0; + blockRead(f1, buf^, bufSize, lResult); + inc(bytesReadIn, lResult); + fs^.vfs_write(d1, buf, lResult); + blockRead(f2, buf^, bufSize, lResult); + inc(bytesReadIn, lResult); + fs^.vfs_write(d2, buf, lResult); + blockRead(f3, buf^, bufSize, lResult); + inc(bytesReadIn, lResult); + fs^.vfs_write(d3, buf, lResult); + + until bytesReadIn = 0; + fs^.vfs_close(d1); + fs^.vfs_close(d2); + freemem(buf, bufSize); + close(f1); + close(f2); +end; + +var searchRec:TSearchRec; + +begin + +// new(drive, init(2880)); // allocate a ramdrive that's 2880 sectors long (floppy size) + new(drive, init(8192*4)); // allocate a ramdrive that's big +// new(drive, init('ubixfs.dsk')); + new(fs, init(drive)); + fs^.vfs_format(drive, 1024, FALSE); + fs^.vfs_init(); + fs^.printSuperBlock(); + fs^.examine(); + + findfirst('*.pas', anyFile, searchrec); + while (doserror = 0) do + begin + if (searchRec.attr and directory <> directory) then copyToFS(searchRec.name); + findNext(searchRec); + end; + test1('1.bmp', '2.bmp'); + copyToFS('3.bmp'); + //test1('4.bmp', '5.bmp'); + test2('4.bmp', '5.bmp', '6.bmp'); + fs^.vfs_mkdir('/foo'); + fs^.vfs_mkdir('/foo/bar'); + fs^.visualize(); + fs^.examine(); + +// fs^.vfs_unlink('/1.bmp'); + dispose(fs, done); + dispose(drive, done); + + writeln('sizeof(diskSuperBlock): ',sizeof(TdiskSuperBlock)); + writeln('sizeof(ubixfsInode): ',sizeof(TubixfsInode)); + writeln('sizeof(dataStream): ', sizeof(TdataStream)); +end. \ No newline at end of file diff --git a/TESTSTR.PAS b/TESTSTR.PAS new file mode 100755 index 0000000..8dc9cbb --- /dev/null +++ b/TESTSTR.PAS @@ -0,0 +1,18 @@ +uses strings; + +var + AGStr : string[15]; + startStr: string[15]; + lenStr : string[15]; + code : integer; + s:string; + ag:integer; + start:integer; + len:integer; + f:file; +begin + assign(f, 'timetest.pas'); + reset(f, 1); + writeln(TFileRec(f).name); + close(f); +end. \ No newline at end of file diff --git a/UBIXFS.PAS b/UBIXFS.PAS new file mode 100755 index 0000000..9c60f7f --- /dev/null +++ b/UBIXFS.PAS @@ -0,0 +1,3481 @@ +unit UbixFS; + +interface +{$DEFINE GRAPHICS} +uses + device, lists {$IFDEF GRAPHICS}, ObjGfx40, ogFont, objImage, DOS, CRT {$ENDIF}; + + // open/fcntl - O_SYNC is only implemented on blocks devices and on files + // located on an ext2 file system +const O_ACCMODE = 00000003; +const O_RDONLY = 00000000; +const O_WRONLY = 00000001; +const O_RDWR = 00000002; +const O_APPEND = 00000010; +const O_CREAT = 00000400; // not fcntl +const O_TRUNC = 00001000; // not fcntl +const O_EXCL = 00002000; // not fcntl +const O_LARGEFILE = 00004000; +const O_SYNC = 00100000; + +// O_NONBLOCK = 00200004; // HPUX has separate NDELAY & NONBLOCK */ +//const O_NDELAY = O_NONBLOCK; +const O_NOCTTY = 00400000; // not fcntl */ +const FASYNC = 00020000; // fcntl, for BSD compatibility */ +const O_DIRECT = 00040000; // direct disk access hint - currently ignored */ +const O_DIRECTORY = 00010000; // must be a directory */ +const O_NOFOLLOW = 00000200; // don't follow links */ +const O_INVISIBLE = 04000000; // invisible I/O, for DMAPI/XDSM + +const F_DUPFD = 0; // dup +const F_GETFD = 1; // get f_flags +const F_SETFD = 2; // set f_flags +const F_GETFL = 3; // more flags (cloexec) +const F_SETFL = 4; +const F_GETLK = 5; +const F_SETLK = 6; +const F_SETLKW = 7; +const F_GETLK64 = 8; +const F_SETLK64 = 9; +const F_SETLKW64 = 10; + + // for F_[GET|SET]FL +const FD_CLOEXEC = 1; // actually anything with low bit set goes + // for posix fcntl() and lockf() +const F_RDLCK = 01; +const F_WRLCK = 02; +const F_UNLCK = 03; + + // for old implementation of bsd flock () +const F_EXLCK = 4; // or 3 +const F_SHLCK = 8; // or 4 + + // for leases +const F_INPROGRESS = 16; + + // operations for bsd flock(), also used by the kernel implementation +const LOCK_SH = 1; // shared lock +const LOCK_EX = 2; // exclusive lock +const LOCK_NB = 4; // or'd with one of the above to prevent blocking +const LOCK_UN = 8; // remove lock + +const LOCK_MAND = 32; // This is a mandatory flock +const LOCK_READ = 64; // ... Which allows concurrent read operations +const LOCK_WRITE = 128; // ... Which allows concurrent write operations +const LOCK_RW = 192; // ... Which allows concurrent read & write ops + +const INODE_NO_FLAG = $00000000; +const INODE_IN_USE = $00000001; + +const ATTR_INODE = $00000004; +const INODE_LOGGED = $00000008; +const INODE_DELETED = $00000010; +const PERMANENT_FLAGS = $0000ffff; +const INODE_NO_CACHE = $00010000; +const INODE_WAS_WRITTEN = $00020000; +const S_IFDIR = $00040000; // POSIX +const NO_TRANSACTION = $00040000; + +const DATASTREAM_DIRECT_BLOCKS = 16; // number of direct blocks in the dataStream + +const UBIXFS_MAGIC_1 = $A0A0A0A; +const UBIXFS_MAGIC_2 = $B0B0B0B; +const UBIXFS_MAGIC_3 = $C0C0C0C; +const UBIXFS_INODE_MAGIC = $3BBE0AD9; + +const UBIXFS_CLEAN = $434C454E; +const UBIXFS_DIRTY = $44495254; + +const SECTOR_SHIFT = 9; +const SECTOR_SIZE = 1 shl SECTOR_SHIFT; + +type + int8 = shortInt; + int16 = smallInt; + int32 = longInt; + + uInt8 = byte; + uInt16 = word; + uInt32 = dWord; +{ Common Integer array definitions } + +type + int8ArrayPtr = ^int8Array; + int8Array = array[ 0..0 ] of int8; + + int16ArrayPtr = ^int16Array; + int16Array = array[ 0..0 ] of int16; + + int32ArrayPtr = ^int32Array; + int32Array = array[ 0..0 ] of int32; + + { Common Unsigned Integer array definitions } + +type + uInt8ArrayPtr = ^uInt8Array; + uInt8Array = array[ 0..0 ] of uInt8; + + uInt16ArrayPtr= ^uInt16Array; + uInt16Array = array[ 0..0 ] of uInt16; + + uInt32ArrayPtr= ^uInt32Array; + uInt32Array = array[ 0..0 ] of uInt32; + +type + flock = object + lType:smallint; + lWhence:smallint; + lStart:integer; + lLen:integer; + lPID:integer; + end; // flock + +type + flock64 = object + lType:smallint; + lWhence:smallint; + lStart:int64; + lLen:int64; + lPID:integer; + end; // flock64 + +(* TBlockRun = packed record + AG : int32; + start : uInt16; + len : uInt16; + end; + + inodeAddr = TblockRun; + + uPtr = packed record + case integer of + 0: (i : int32); + 1: (u : uInt32); + 2: (s : single); + 3: (d : double); + 4: (p : pointer); + 5: (offset : int64); + 6: (iAddr : inodeAddr); + end; +*) + PDiskSuperBlock = ^TDiskSuperBlock; + TDiskSuperBlock = packed record + name : array[0..31] of char; + magic1 : int32; + + fsByteOrder : int32; + + // blockSize on disk + blockSize : uInt32; + + // number of bits needed to shift a block number to get a byte address + blockShift : uInt32; + + numBlocks : int64; + usedBlocks : int64; + + inodeSize : uInt32; + magic2 : uInt32; + + blocksPerAG : uInt32; + AGShift : uInt32; + numAGs : uInt32; + + flags : uInt32; + + logBlocks : TBlockRun; + logStart : int64; + logEnd : int64; + + magic3 : int32; + BAT : inodeAddr; + rootDir : inodeAddr; + indicies : inodeAddr; + + pad : array[0..371] of byte; + end; // TDiskSuperBlock + + PDataStream = ^TDataStream; + TDataStream = packed record + direct : array[0..DATASTREAM_DIRECT_BLOCKS-1] of TBlockRun; + maxDirect : int64; + indirect : TBlockRun; + maxIndirect : int64; + doubleIndirect: TBlockrun; + maxDIndirect : int64; + size : int64; + blockCount : uInt32; + end; // TDataStream + + PUbixfsInode = ^TUbixfsInode; + TUbixfsInode = packed record + magic : int32; + inodeNum : inodeAddr; + uid : uInt32; + gid : uInt32; + mode : int32; + flags : int32; + createTime : int64; + modifiedTime : int64; + parent : inodeAddr; + attributes : inodeAddr; + iType : uInt32; + inodeSize : uInt32; + data : uPtr; {this was the etc field in bfs} + blocks : TDataStream; + name : array[0..255] of char; + smallData : array[0..0] of byte; + end; // TUbixfsInode + + PCacheNode = ^TCacheNode; + TCacheNode = object + public + address : int64; + lastUsed : int64; + data : pointer; + dataSize : uInt32; + dirty : boolean; + locked : boolean; + constructor init(_address, _lastUsed:int64; _dataSize:uInt32); + destructor done; + end; // TCacheNode + + PVNode = ^TVNode; + TVNode = object + TID : uInt32; // Thread ID + iAddr : inodeAddr; + position : int64; + flags : uInt32; + end; // TVNode + + PVnid = ^TVnid; + TVnid = object + refCount : uInt32; + iAddr : inodeAddr; + mode : uInt32; + end; // TVnid + + PUbixFS = ^TUbixFS; + TUbixFS = object + private + dev : PDevice; + superBlock : PDiskSuperBlock; + blockCache : PbTree; + blockTime : PbTree; + vnids : PbTree; + vNodeQueue : PQueue; + fileDescriptors : ^array[0..0] of TVNode; + + maxCacheEntry : uInt32; + maxFDEntry : uInt32; + + procedure assert(x:boolean); + function allocBRun(blockRun:TBlockRun):TBlockRun; + function allocInode(parentIAddr:inodeAddr):PUbixfsInode; + function cacheBlock(blockAddr:TBlockRun):pointer; + function extendFile(inode:PUbixfsInode; newSize:int64):boolean; + function findBRun(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunDirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunDIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function freeUsedBlocks(blockRun:TBlockRun):integer; + function getFileSize(inode:PUbixfsInode):int64; + function getFileSpace(inode:PUbixfsInode):int64; + function iAddrToStr(iAddr:inodeAddr):string; + function isBAT(iAddr:inodeAddr):boolean; + function isValidIAddr(iAddr:inodeAddr):boolean; + function loadBlock(blockAddr:TBlockRun):pointer; + + function markUsedBlocks(blockRun:TBlockRun):integer; + function nextAG(AG:uInt32):uInt32; + function pathToIAddr(path:PChar):inodeAddr; + + function readDataStream(iAddr:inodeAddr; position:int64; buf:pointer; length:uInt32):integer; + function saveBlock(blockAddr:TBlockRun; buf:pointer):boolean; + procedure setBlockRun(var blockRun:TBlockRun; AG:uInt32; start, len:uInt16); + procedure setIAddr(var inode:inodeAddr; AG:uInt32; start, len:uInt16); + function shrinkFile(inode:PUbixfsInode):boolean; + function strToIAddr(s:string):inodeAddr; + function writeDataStream(iAddr:inodeAddr; position:int64; buf:pointer; length:uInt32):integer; + public + constructor init(device:Pdevice); + procedure printSuperBlock; + procedure assign(var F:TFileRec; const _name:string); + procedure reset(var F:TFileRec; blkSize:uInt32); + procedure rewrite(var F:TFileRec; blkSize:uInt32); + procedure sync; + function syncBlock(cacheNode:PCacheNode):boolean; + {$IFDEF GRAPHICS} + procedure visualize; + {$ENDIF} + function vfs_fCreate(fileName:PChar):integer; + function vfs_fExist(path:PChar):integer; + function vfs_close(handle:integer):integer; + function vfs_format(device:PDevice; fsBlockSize:uInt32; quick:boolean):integer; + function vfs_init:integer; + function vfs_mkdir(path:PChar):integer; + function vfs_read(handle:integer; buffer:pointer; size:uInt32):integer; + function vfs_seek(handle:integer; newPosition:int64):integer; + function vfs_unlink(fileName:PChar):integer; + function vfs_write(handle:integer; buffer:pointer; size:uInt32):integer; + procedure examine; + destructor done; virtual; + end; // TUbixFS + +implementation + +uses strings, dos, math, lists; + +// IMPORTANT NOTE +// You cannot under any circumstance change NUM_EXTENTION_BLOCKS. This +// constant defines the multiple of blocks that a file will be extended +// by, *and* it determines how many blocks are used in each blockRun in +// the Double Indirect dataStream. +// NUM_EXTENSION_BLOCKS determines how much slack space is allocated for +// a file when it is extended. For example, a file with 0 bytes in it +// will take up 0 blocks for the data and 1 block for the inode (ignoring +// space required to hold the directory entry). When the first byte is +// written, extendFile() will allocate a multiple of NUM_EXTENSION_BLOCKS +// to accomodate the amount of data being written out. So, a 1 byte file will +// actually take up NUM_EXTENSION_BLOCKS (plus one for the inode) until the +// file is closed. Then the file will be trimmed and the slack space reclaimed. +// This value is also used in the double-indirect region of the dataStream +// to determine how many blocks each blockRun uses. + +// vv DO NOT CHANGE vv // +const NUM_EXTENSION_BLOCKS = 4; // <--- don't change this +// ^^ DO NOT CHANGE ^^ // + +const NUM_CACHE_ENTRIES = 8192; // default max entries in block cache +const NUM_FD_ENTRIES = 1024; // default max entries in the File Descriptor Table +const BAT_PAGE_SIZE = 256; // page size for the Block Allocation Tree + +type + PUbixBTreeVFS = ^TUbixBTreeVFS; + TUbixBTreeVFS = object(TBTreeVFS) + private + fs : PUbixFS; + iAddr : inodeAddr; + position : int64; + openFile : boolean; + public + constructor init(_fs:PUbixFS); + function fClose:boolean; virtual; + function fCreate(const filename:string):boolean; virtual; + function fExist(const filename:string):boolean; virtual; + function fOpen(const filename:string):boolean; virtual; + function fRead(var buf; size:uInt32):boolean; virtual; + function fSeek(offset:uInt32):boolean; virtual; + function fWrite(var buf; size:uInt32):boolean; virtual; + destructor done; virtual; + end; // TUbixBTreeVFS + +function getRDTSCTime:int64; +var x:int64; +begin + asm + rdtsc + mov dword ptr x, eax + mov dword ptr x+4, edx + end; + result := x; +end; + +function null_file_proc conv arg_based (f: LongInt; buf: pointer; len: ULong; var act: ULong): LongInt; +begin + runerror(999); +end; + +// B^Tree compareKey BlockRun function +function compareKeyBlockRun(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + + if (TBlockRun(key1^).AG < TBlockRun(key2^).AG) then + result := -1 + else + if (TBlockRun(key1^).AG > TBlockRun(key2^).AG) then + result := 1 + else // AGs are the same + if (TBlockRun(key1^).start < TBlockRun(key2^).start) then + result := -1 + else + if (TBlockRun(key1^).start > TBlockRun(key2^).start) then + result := 1; // else they are identical +end; // compareKeyInt64 + +// B^Tree copyKey BlockRun function + +procedure copyKeyBlockRun(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + TBlockRun(destKey^) := TBlockRun(srcKey^); +end; // copyKeyBlockRun; + +// B^Tree keySize BlockRun function + +function keySizeBlockRun(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(TBlockRun); +end; // keySizeBlockRun + + +constructor TCacheNode.init; +begin + address := _address; + lastUsed := _lastUsed; + dataSize := _dataSize; + data := NIL; + if (dataSize <> 0) then GetMem(data, dataSize); + dirty := FALSE; + locked := FALSE; +end; // TCacheNode.init + +destructor TCacheNode.done; +begin + if (data <> NIL) and (dataSize <> 0) then FreeMem(data, dataSize); + address := 0; + lastUsed := 0; + data := NIL; + dataSize := 0; + dirty := FALSE; + locked := FALSE; +end; // TCacheNode.done + +constructor TUbixBTreeVFS.init; +begin +// writeln('TUbixBTreeVFS.init()'); + fs := _fs; + fillchar(iAddr, sizeof(iAddr), 0); + position := 0; + openFile := FALSE; +end; // TUbixBTreeVFS.init; + +function TUbixBTreeVFS.fClose; +var + tmpIAddr:inodeAddr; + tmpInode:PUbixfsInode; +begin + result := FALSE; + if (fs = NIL) then exit; + if (openFile) then + begin + if not fs^.isValidIAddr(IAddr) then exit; + if not fs^.isBAT(IAddr) then // Don't shrink the BAT + begin + tmpInode := fs^.loadBlock(IAddr); + if (tmpInode = NIL) then exit; + fs^.shrinkFile(tmpInode); // shrink the directory + end; + position := 0; + openFile := FALSE; + end; + result := TRUE; +end; // TUbixBTreeVFS.fClose + +function TUbixBTreeVFS.fCreate; +begin + // I *think* that this code is only called for the + // B^Tree (and BAT). Therefore, the file technically exists, but just + // has nothing in it when we call fCreate. + result := fOpen(filename); +end; // TUbixBTreeVFS.fCreate + +function TUbixBTreeVFS.fExist; +var + tmpIAddr:inodeAddr; + tmpInode:PUbixfsInode; +begin + result := FALSE; + assert(fs <> NIL); + if (fs = NIL) then exit; + assert(fs^.superBlock <> NIL); + if (fs^.superBlock = NIL) then exit; + tmpIAddr := fs^.strToIAddr(filename); + if not fs^.isValidIAddr(tmpIAddr) then exit; + + // If we've created an inode, but haven't written any data to it, then + // the B^Tree hasn't written out a superblock, so technically the file doesn't + // exist, and this is a really long run-on sentence. + tmpInode := fs^.loadBlock(tmpIAddr); + + // I'm not sure why, but the compiler cannot properly evaluate (tmpInode^.blocks.size <> 0), + // so we check both the high and low portions of the size to make sure it's not 0. + // result := (tmpInode <> NIL) and ((longLongRec(tmpInode^.blocks.size).lo <> 0) or (longLongRec(tmpInode^.blocks.size).hi <> 0)); + + // However, checking for 0 and then taking the complement of that works .. so we go that way. + result := ((tmpInode <> NIL) and (not (tmpInode^.blocks.size = 0))); +end; // TUbixBTreeVFS.fExist + +function TUbixBTreeVFS.fOpen; +begin + result := FALSE; + if (fs = NIL) then exit; + if (fs^.superBlock = NIL) then exit; + iAddr := fs^.strToIAddr(filename); + position := 0; + openFile := fs^.isValidIAddr(iAddr); + result := openFile; /// fs^.isValidIAddr(iAddr); +end; // TUbixBTreeVFS.fOpen + +function TUbixBTreeVFS.fRead; +begin + result := FALSE; + if (fs = NIL) then exit; +// with inode^.inodeNum do +// write('fRead(', AG, ':', start, ':', len, ', '); +// write(position, ', '); +// write('@buf, '); + + result := (fs^.readDataStream(iAddr, position, @buf, size) = 0); + position := position + int64(size); +end; // TUbixBTreeVFS.fRead + +function TUbixBTreeVFS.fSeek; +begin + // uhm.. if the desired offset is beyond the end of the file, + // should we not extend out the file? Or does that occur in writeDataStream ... + position := offset; + result := TRUE; +end; // TUbixBTreeVFS.fSeek + +function TUbixBTreeVFS.fWrite; +begin + result := FALSE; + if (fs = NIL) then exit; +// with inode^.inodeNum do +// write('fWrite(', AG, ':', start, ':', len, ', '); +// write(position, ', '); +// write('@buf, '); + result := (fs^.writeDataStream(iAddr, position, @buf, size) = 0); + position := position + int64(size); + //inc(position, size); +end; // TUbixBTreeVFS.fWrite; + +destructor TUbixBTreeVFS.done; +begin + // XXX: It's possible that we need to save the inode here since + // we possibly changed a directory + fClose(); // "Close" the file if it hasn't been already. + fillchar(iAddr, sizeof(iAddr), 0); + fs := NIL; // don't destruct it since we didn't allocate it +end; // TUbixBTreeVFS.done + +constructor TUbixFS.init; +begin + dev := device; + superBlock := NIL; + blockCache := NIL; + blockTime := NIL; + vnids := NIL; + vnodeQueue := NIL; + fileDescriptors := NIL; + maxCacheEntry := NUM_CACHE_ENTRIES; + maxFDEntry := NUM_FD_ENTRIES; +end; // TUbixFS.init + +procedure TUbixFS.assert(x:boolean); +begin + if not x then runerror(1000); +end; // TUbixFS.assert + +{$system} +procedure TUbixFS.assign; +begin + with f do begin + magic := @f; + state := [%file_assigned]; + name := _name; + rd_proc := null_file_proc; + wr_proc := null_file_proc; + check_magic(); + end; // with +end; // TUbixFS.assign + +{$system} +procedure TUbixFS.reset; +begin +// if IO_ApiRet <> 0 then IO_Error(IO_Apiret); +end; // TUbixFS.reset + +procedure TUbixFS.rewrite; +begin + +end; // TUbixFS.rewrite + +function TUbixFS.allocBRun; +var + FBL:PBTree; // free block list + u:uPtr; + tmpBlockRun:TBlockRun; + searchRec:BTreeSearchRec; + found:boolean; +begin + setBlockRun(result, 0, 0, 0); + // basic sanity checks +with blockRun do + writeln('allocBRun(', AG, ':', start, ':', len, ');'); + if (superBlock = NIL) or (blockRun.len = 0) then exit; + if not isValidIAddr(superBlock^.BAT) then exit; + + if (blockRun.AG > superBlock^.numAGs) then exit; + + if (superBlock^.usedBlocks + int64(blockRun.len) > superBlock^.numBlocks) then exit; // should probably give a louder warning + + new(FBL, open(iAddrToStr(superBlock^.BAT), new(PUbixBTreeVFS, init(@self)))); + //new(FBL, open('BAT.DAT', NIL)); + if (FBL = NIL) then exit; + + FBL^.InstallUserFunctions(compareKeyBlockRun, copyKeyBlockRun, keySizeBlockRun); + setBlockRun(tmpBlockRun, blockRun.AG, 0, 0); + + fillchar(searchRec, sizeof(searchRec), 0); + + // tmpBlockRun points to the Allocation Group marker in the tree + if not FBL^.FindFirst(@tmpBlockRun, searchRec) then + begin + writeln('Could not find AG marker inside FBL'); + dispose(FBL, done); + exit; + end; + + found := FALSE; // have we found a free range large enough to accomodate our request? + repeat + if not FBL^.findNext(searchRec) then assert(FBL^.GetFirstKey(searchRec)); + // assert(searchRec.key <> NIL); // I don't think this can ever happen + with TBlockRun(searchRec.key^) do + if (len = 0) then + if (AG = blockRun.AG) then + break // We've wrapped completely around + else + continue; // look for another one + found := (TBlockRun(searchRec.key^).len >= blockRun.len); + until found; +(* + found = false; + do { + if (!FBL->FindNext(searchRec)) assert(FBL->GetFirstKey(searchRec)); + if (searchRec.key->len == 0) + if (searchRec.key->AG == blockRun.AG) break; else continue; + found = (searchRec.key->len >= blockRun.len); + } while (!found) +*) + // It's possible we didn't find a run of the size requested.. + if found then + begin + // No matter what, we're going to have to delete the old entry and insert a new one + + tmpBlockRun := TBlockRun(searchRec.key^); // make a copy + u := searchRec.value; + FBL^.Delete(@tmpBlockRun, u); // delete the old key + with tmpBlockRun do + begin + setBlockRun(result, AG, start, blockRun.len); // set the result to the associated blockrun + inc(start, blockRun.len); + dec(len, blockRun.len); + // I'm not sure what to put in the value field yet + if (len <> 0) then assert(FBL^.Insert(@tmpBlockRun, u)); // if there's any blocks left, re-insert them into the FBL + superBlock^.usedBlocks := superBlock^.usedBlocks + int64(blockRun.len); + end; // with + end; // if found + FBL^.FindClose(searchRec); // de-allocate the searchRec + dispose(FBL, done); +end; // TUbixFS.allocBRun + +(* +function TUbixFS.allocBRun; +var + FBL:int8ArrayPtr; // Free Block List array pointer + FBLSize:uInt32; // Free Block List array pointer size + bitOffset:uInt32; + byteOffset:uInt32; + curBlock, baseBlock:uInt32; + freeCount:uInt32; +begin + // Notes: + // 2005-04-27 mji + // allocBRun() will allocate a blockRun that is near to the blockRun + // passed into the function. The AG and Start field of blockRun will be + // the starting point of where to look for free blocks. The len field + // holds how many contiguous blocks we need to allocate. + // We look through the BAT (Block Allocation Table) for a contiguous run that + // fits the requested length. The BAT is a bitfield (used blocks are an + // on (1) bit, free blocks are an off (0) bit) of used/unused logical + // blocks, where the logical block size was set when the device was formatted. + // This shouldn't be confused with the sector size on the device, which + // is always 512 bytes. Block sizes are usually 1k, but can be any power + // of 2 higher than that up to 8k. Since we only need to load one AG section + // out of the BAT at a time, we only allocate one block worth of memory. + // The disk is broken up into Allocation Groups, where each allocation group + // bitfield fits into a single block. This means that for a device with 1k + // blocks an AG will be 8192 blocks long. Small devices (such as a floppy) + // will only have 1 AG. + // To find a free run, we load a starting section of the BAT out of the + // BAT file. The BAT is technically another file on the volume, but unlike + // normal files the BAT inode is referenced inside the superblock, and cannot + // be touched by a normal user (if the volume is grown later, the BAT can be + // grown as well). + // Once the AG block list is loaded, we scan through looking for a contiguous + // set of free blocks that fits our length requirement. In the event this + // AG doesn't meet that requirement, we move to the next AG. When we hit the + // end of the volume, we go back to the beginning of the volume and continue + // scanning until either we find a free run or we end up back where we began. + // In the latter case, the disk would have to either be very fragmented or + // full. If we succeed, we mark the blocks as used and return the blockRun. + + // XXX To do: + // Might want to clear the blocks we allocated to avoid garbage from being present + // We also need to ensure that a run doesn't cross an AG. This is because some + // other pieces of code do not take into account the possibility of moving to the + // next AG (or wrapping around completely). This change will have to be + // coupled with a change in extendFile() to ask for decreasing block counts until one + // is found that fits inside an AG. + + setBlockRun(result, 0, 0, 0); + if (superBlock = NIL) then exit; + if (blockRun.AG > superBlock^.numAGs) then exit; + if (superBlock^.usedBlocks + int64(blockRun.len) > superBlock^.numBlocks) then exit; // should probably give a louder warning + + // we read in one block at a time + FBLSize := superBlock^.blockSize; + GetMem(FBL, FBLSize); + assert(FBL <> NIL); + fillchar(FBL^, FBLSize, 0); // better safe than sorry + assert(readDataStream(superBlock^.BAT, (blockRun.AG shl superblock^.AGShift) shr 3, FBL, FBLSize) = 0); + byteOffset := blockRun.start shr 3; + bitOffset := 7-(blockRun.start and 7); + baseBlock := (blockRun.AG shl superBlock^.AGShift) + blockRun.start; + curBlock := baseBlock; + freeCount := 0; + // an optimization would be to check to see if the byte is -1, thus skipping 8 at a time + // This will need to be implemented (but can be later) + repeat + + inc(curBlock); + + if (FBL^[byteOffset] and (1 shl bitOffset) = 0) then + inc(freeCount) + else + freeCount := 0; + + if (freeCount = blockRun.len) then break; // did we meet our length requirement? + + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + + if (byteOffset = superBlock^.blockSize) then + begin + // We scanned through one Allocation Group of blocks. Move to the next one + // XXX + // We have a few options for crossing an AG. + // 1) Reset free count to 0. If there's not a run long enough to handle the requested + // blockRun length, throw it back to extendFile() and let it break up the run into smaller chunks + // 2) Return the longest run we've encountered so far. This would make extendFile() loop until + // all the block requests were accomodated. + // 3) Rewrite this function to use some sort of B^Tree in a similar way to how XFS + // does it. (Not for the faint of heart, and might not be possible). + inc(blockRun.AG); + + // it's possible that we're wrapping back around to the beginning. + if (blockRun.AG = superBlock^.numAGs) then + begin + // We hit the end of the volume and wrapped back around + blockRun.AG := 0; // Back to the beginning AG + curBlock := 0; // curBlock is from the base of the volume + freeCount := 0; // reset this to 0 + end; + // read in the FBL for the AG we're currently on + assert(readDataStream(superBlock^.BAT, (blockRun.AG shl superblock^.AGShift) shr 3, FBL, FBLSize) = 0); + byteOffset := 0; // first byte in this block + bitOffset := 7; // first bit of the first byte + end; // if byteOffset = superBlock^.blocksPerAG + + until curBlock = baseBlock; // repeat until we end up back where we started + + if (freeCount = blockRun.len) then // we found a run + begin + blockRun.AG := (curBlock-blockRun.len) shr superBlock^.AGShift; + blockRun.start := (curBlock-blockRun.len) and (superBlock^.blocksPerAG-1); + markUsedBlocks(blockRun); + result := blockRun; + end + else writeln('Failure in allocBRun()'); + FreeMem(FBL, FBLSize); + +end; // TUbixFS.allocBRun +*) +function TUbixFS.allocInode; +var + inode:PUbixfsInode; + blockRun:TBlockRun; + +begin + // XXX ToDo: + // need to fill in the rest of the fields in the inode record + + // Notes: + // 2005-04-27 mji + // Inodes take up exactly one logical block on the device. They + // are usually in the same AG as their parent directory. Allocating + // one involves getting some memory for it and finding a free block + // to hold it on the device. + + result := NIL; + if (superBlock = NIL) then exit; + assert(parentIAddr.AG < superBlock^.numAGs); + if (parentIAddr.AG >= superBlock^.numAGs) then exit; // parent addr is outside out block count? + // ask for a single block somewhere around the beginning of the requested AG + setBlockRun(blockRun, parentIAddr.AG, 0, 1); + blockRun := allocBRun(blockRun); + assert(blockRun.len <> 0); + if (blockRun.len = 0) then exit; // this would be bad + // Normally we'd allocate memory for the inode. Instead we let loadBlock() do that + // for us. + //inode := loadBlock(blockRun); // candidate for cacheBlock() + inode := cacheBlock(blockRun); + + assert(inode <> NIL); + if (inode = NIL) then exit; + fillchar(inode^, superBlock^.inodeSize, 0); + + with inode^ do + begin + magic := UBIXFS_INODE_MAGIC; + + // inodes point to themselves + inodeNum := blockRun; + + uid := 0; // this doesn't mean much here + gid := 0; + // mode := ?? + flags := INODE_NO_FLAG; + // createTime := + // modifiedTime := + + parent := parentIAddr; + setIAddr(attributes, 0, 0, 0); + inodeSize := superBlock^.inodeSize; + // data := 0; + // there's no data yet in the file, so don't + // set blocks to anything (cleared above) + blocks.blockCount := 0; + + end; // with + result := inode; +end; // TUbixFS.allocInode + +function TUbixFS.cacheBlock; +var + blockAddress:int64; + cacheNode:PCacheNode; + u:uPtr; + searchRec:BTreeSearchRec; +begin + result := NIL; // failure by default + + if (dev = NIL) or (superBLock = NIL) or (blockcache = NIL) then exit; + + with blockAddr, superBlock^ do + blockAddress := (AG shl AGShift) + start; + + fillchar(u, sizeof(u), 0); // jut to be safe + cacheNode := NIL; + + if (blockCache^.Find(@blockAddress, u)) then + begin + // block was in cache + assert(u.p <> NIL); + cacheNode := u.p; + assert(cacheNode^.data <> NIL); + fillchar(cacheNode^.data^, superBlock^.blockSize, 0); + cacheNode^.dirty := TRUE; + // Now we need to update the blockTime. We do this by first deleting + // the cacheNode out of the blockTime cache. + blockTime^.Delete(@cacheNode^.lastUsed, u); // Delete old one + cacheNode^.lastUsed := getRDTSCTime(); // Get the new timestamp + blockTime^.Insert(@cacheNode^.lastUsed, u); // Insert with new time + end + else + begin + // Requested block isn't in cache + + // Check to make sure cache isn't full + + if (blockCache^.getKeyCount() = maxCacheEntry) then + begin + blockTime^.getFirstKey(searchRec); // least recently used block + cacheNode := searchRec.value.p; // get the pointer from the value + assert(syncBlock(cacheNode)); +(* + cacheNode := searchRec.value.p; // get the pointer from the value + assert(cacheNode <> NIL); + // I suppose we could write a syncBlock() function, since this + // code is also duplicated in sync(). + if (cacheNode^.dirty) then + begin + with cacheNode^ do + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + cacheNode^.dirty := FALSE; + end; // if dirty *) + + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + blockTime^.Delete(@cacheNode^.lastUsed, u); + // We've deleted the cacheNode out of both the blockCache and + // the blockTime trees. We're going to re-use the cacheNode. + cacheNode^.address := blockAddress; + cacheNode^.lastUsed := getRDTSCTime(); + // No need to update data, dataSize, or dirty. We re-use the first two, + // and dirty will be set to FALSE above if necessary. Might need to + // reset locked if we're using that. + end + else + new(cacheNode, init(blockAddress, getRDTSCTime(), superBlock^.blockSize)); + + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + u.p := cacheNode; + if not (blockCache^.Insert(@blockAddress, u)) then + writeln('Error inserting ', blockAddress, ' into blockCache in cacheBlock()'); + if not (blockTime^.Insert(@cacheNode^.lastUsed, u)) then + writeln('Error inserting into blockTime'); + end; + + if (cacheNode <> NIL) then + begin + fillchar(cacheNode^.data^, superBlock^.blockSize, 0); + cacheNode^.dirty := TRUE; // mark this block as dirty + result := cacheNode^.data; // return the data pointer + end; + +end; // TUbixFS.cacheBlock + +function TUbixFS.extendFile; + + function appendToDIndirect(newBlockRun:TBlockRun):boolean; + var + extents, indirectExtents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + extentsIndex, indirectExtentsIndex:uInt32; + extentsBlockIndex, indirectExtentsBlockIndex:uInt32; + extentsUpperBound:uInt32; + tmpBlockRun:TBlockRun; + begin +writeln('in appendToDIndirect()'); + // Many apologies for the long variable names. This section is relatively + // complicated, and it wouldn't help to have cryptic variables. + result := FALSE; // failure by default + if (newBlockRun.len mod NUM_EXTENSION_BLOCKS <> 0) then + begin + writeln('Double indirect block run is not a multiple of NUM_EXTENSION_BLOCKS'); + exit; + end; + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun)) - 1; + + if not isValidIAddr(inode^.blocks.doubleIndirect) then + begin + // allocate a double indirect block + tmpBlockRun := inode^.inodeNum; // allocate a block near the parent inode + tmpBlockRun.len := NUM_EXTENSION_BLOCKS; + inode^.blocks.doubleIndirect := allocBRun(tmpBlockRun); + + if not isValidIAddr(inode^.blocks.doubleIndirect) then + begin + writeln('Failed to allocate double indirect block'); + exit + end; + tmpBlockRun := inode^.blocks.doubleIndirect; + for extentsBlockIndex := low(extents) to high(extents) do + begin + extents[extentsBlockIndex] := cacheBlock(tmpBlockRun); + //extents[extentsBlockIndex] := loadBlock(tmpBlockRun); // good candidate for cacheBlock() + //fillchar(extents[extentsBlockIndex]^, superBlock^.blockSize, 0); + //assert(saveBlock(tmpBlockRun, extents[extentsBlockIndex])); + inc(tmpBlockRun.len); + end; // for extentsBlockIndex + + // We have a freshly allocated double indirect block. Now we need an indirect block + // tmpBlockRun := inode^.blocks.doubleIndirect; // allocate an indirect block near the dindirect block + // tmpBlockRun.len := NUM_EXTENSION_BLOCKS; // theoretically, it is already + // extents[0]^[0] := allocBRun(tmpBlockRun); + extents[0]^[0] := allocBRun(inode^.blocks.doubleIndirect); + if not isValidIAddr(extents[0]^[0]) then + begin + writeln('Failed to allocate indirect block beneath a double indirect block'); + exit + end; + // save out the doubleIndirect block + inode^.blocks.maxDIndirect := inode^.blocks.maxIndirect; + //assert(saveBlock(inode^.blocks.doubleIndirect, extents)); + // This is a little misleading. Since saveBlock() formulates the + // address based on tbe base-block of the blockRun, this will only save + // out the first block. Secondly, we may not even need to do this since + // we tagged them for saving above. + assert(saveBlock(inode^.blocks.doubleIndirect, extents[0])); + + tmpBlockRun := extents[0]^[0]; + for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + begin + indirectExtents[indirectExtentsBlockIndex] := cacheBlock(tmpBlockRun); // good candidate for cacheBlock() + (* indirectExtents[indirectExtentsBlockIndex] := loadBlock(tmpBlockRun); // good candidate for cacheBlock() + fillchar(indirectExtents[indirectExtentsBlockIndex]^, superBlock^.blockSize, 0); + assert(saveBlock(tmpBlockRun, indirectExtents[indirectExtentsBlockIndex])); *) + inc(tmpBlockRun.start); + end; // for indirectExtentsBlockIndex + + // Because we have a freshly allocated double indirect block, we have no entries + // in it yet. This means that our DIndirect index is 0, and our indirect index is also 0. + extentsIndex := 0; + extentsBlockIndex := 0; + indirectExtentsIndex := 0; + indirectExtentsBlockIndex := 0; + end + else + begin + // double indirect block exists. Load it + tmpBlockRun := inode^.blocks.doubleIndirect; + for extentsBlockIndex := low(extents) to high(extents) do + begin + extents[extentsBlockindex] := loadBlock(tmpBlockRun); + assert(extents[extentsBlockindex] <> NIL); + inc(tmpBlockRun.start); + end; // for extentsBlockIndex + + // Now scan through (backwards) to find the last entry. + for extentsBlockIndex := high(extents) downto low(extents) do + if (extents[extentsBlockIndex]^[0].len <> 0) then break; + + for extentsIndex := extentsUpperBound downto 0 do + if (extents[extentsBlockIndex]^[extentsIndex].len <> 0) then break; + // [extentsBlockindex],[extentsIndex] points to the entry which holds the indirect block with the last entry. + // load that one + tmpBlockRun := extents[extentsBlockindex]^[extentsIndex]; + for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + begin + indirectExtents[indirectExtentsBlockIndex] := loadBlock(tmpBlockRun); + assert(indirectExtents[indirectExtentsBlockIndex] <> NIL); + inc(tmpBlockRun.start); + end; // for indirectExtentsBlocksIndex + + // first check the last entry + if (indirectExtents[high(indirectExtents)]^[extentsUpperBound].len <> 0) then + begin + // this indirect block is full. Allocate a new one + if ((extentsIndex = extentsUpperBound) and (extentsBlockIndex = high(extents))) then + begin + // I might as well halt at this point because the user is totally hosed. + writeln('Egad! The double indirect block section is full. Can not write any more to this file!'); + exit; + end; // if doomed + tmpBlockRun := extents[extentsBlockIndex]^[extentsIndex]; // address of the aforementioned full indirect block + // tmpBlockRun.len := NUM_EXTENSION_BLOCKS; // should be already + if (extentsIndex = extentsUpperBound) then + begin + extentsIndex := 0; + inc(extentsBlockIndex); // checked above to see if this was full + end + else + inc(extentsIndex); + + extents[extentsBlockIndex]^[extentsIndex] := allocBRun(tmpBlockRun); + tmpBlockRun := extents[extentsBlockIndex]^[extentsIndex]; // grab a copy of the brun we just made + assert(isValidIAddr(tmpBlockRun)); // better safe than sorry + for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + begin + indirectExtents[indirectExtentsBlockIndex] := cacheBlock(tmpBlockRun); // good candidate for cacheBlock() + (* indirectExtents[indirectExtentsBlockIndex] := loadBlock(tmpBlockRun); // good candidate for cacheBlock() + fillchar(indirectExtents[indirectExtentsBlockIndex]^, superBlock^.blockSize, 0); + assert(saveBlock(tmpBlockRun, indirectExtents[indirectExtentsBlockIndex])); *) + inc(tmpBlockRun.start); + end; + tmpBlockRun := inode^.blocks.doubleIndirect; + inc(tmpBlockRun.start, extentsBlockIndex); + saveBlock(tmpBlockRun, extents[extentsBlockIndex]); // save, since we added an indirect block to it + indirectExtentsIndex := 0; + indirectExtentsBlockIndex := 0; + end + else + begin + // now scan forward + indirectExtentsBlockIndex := 0; + indirectExtentsIndex := 0; + while (indirectExtents[indirectExtentsBlockIndex]^[indirectExtentsIndex].len <> 0) do + begin + if (indirectExtentsIndex = extentsUpperBound) then + begin + indirectExtentsIndex := 0; + if (indirectExtentsBlockIndex = high(indirectExtents)) then break else inc(indirectExtentsBlockIndex); + end + else + inc(indirectExtentsIndex); + end; // while +(* for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + for indirectExtentsIndex := 0 to extentsUpperBound do + if (indirectExtents[indirectExtentsBlockIndex]^[indirectExtentsIndex].len = 0) then break; *) +writeln('[1] indirectExtentsIndex: ', indirectExtentsIndex); +writeln('[1] indirectExtentsBlockIndex: ', indirectExtentsBlockIndex); + + end; + // [extentsBlockIndex],[extentsIndex] points to which indirect block we're on + // [indirectExtentsBlockIndex],[indirectExtentsIndex] points to the first free entry in that indirect block + end; + + while (newBlockRun.len <> 0) do + begin + tmpBlockRun := newBlockRun; + tmpBlockRun.len := NUM_EXTENSION_BLOCKS; +writeln('indirectExtentsIndex: ', indirectExtentsIndex); +writeln('indirectExtentsBlockIndex: ', indirectExtentsBlockIndex); + indirectExtents[indirectExtentsBlockIndex]^[indirectExtentsIndex] := tmpBlockRun; + with newBlockRun do + begin + dec(len, NUM_EXTENSION_BLOCKS); + if ((uInt32(start) + NUM_EXTENSION_BLOCKS) > high(start)) then inc(AG); + inc(start, NUM_EXTENSION_BLOCKS); // Warning! Relies on integer wrap-around (start is 16-bit) + end; // with + + if (indirectExtentsIndex = extentsUpperBound) then + begin + // this indirect block is full. + // Save the current one + assert(saveBlock(extents[extentsBlockIndex]^[extentsIndex], indirectExtents[indirectExtentsBlockIndex])); + if (indirectExtentsBlockIndex = high(indirectExtents)) then + begin + // Allocate a new one + if ((extentsIndex = extentsUpperBound) and (extentsBlockIndex = high(extents))) then + begin + // I might as well halt at this point because the user is totally hosed. + writeln('Egad! The double indirect block section is full. Can not write any more to this file!'); + exit; + end; // if doomed + tmpBlockRun := extents[extentsBlockIndex]^[extentsIndex]; // snag the previous location + // update which index in the double indirect block we're using + if (extentsIndex = extentsUpperBound) then + begin + inc(extentsBlockIndex); + extentsIndex := 0; + end + else + inc(extentsIndex); + + extents[extentsBlockIndex]^[extentsIndex] := allocBRun(tmpBlockRun); // allocate an indirect block near the previous one + // make a copy of the indirect block we just allocated + tmpBlockRun := extents[extentsBlockIndex]^[extentsIndex]; + assert(isValidIAddr(tmpBlockRun)); // just to be sure + for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + begin + indirectExtents[indirectExtentsBlockIndex] := cacheBlock(tmpBlockRun); // candidate for cacheBlock() + (* indirectExtents[indirectExtentsBlockIndex] := loadBlock(tmpBlockRun); // candidate for cacheBlock() + assert(indirectExtents[indirectExtentsBlockIndex] <> NIL); + fillchar(indirectExtents[indirectExtentsBlockIndex]^, superBlock^.blockSize, 0); + assert(saveBlock(tmpBlockRun, indirectExtents[indirectExtentsBlockIndex])); *) + inc(tmpBlockRun.start); + end; // for indirectExtentsBlockIndex + indirectExtentsIndex := 0; + indirectExtentsBlockIndex := 0; + // Here we need to save the extents block we just changed --> + tmpBlockRun := inode^.blocks.doubleIndirect; + inc(tmpBlockRun.start, extentsBlockIndex); + assert(saveBlock(tmpBlockRun, extents[extentsBlockIndex])); + // <--- + end + else + begin + // We just need to pop over to the next section of the indirect block. + // At this point I really want to beat the BeOS guy who decided to make these strucutres + // span multiple disk blocks. They said [in the book] that they didn't want to do that because + // it complicated things. Well, gee, no kidding. + inc(indirectExtentsBlockIndex); + indirectExtentsIndex := 0; + end; + end + else + inc(indirectExtentsIndex); + // update the size the maxDoubleIndirect blocks reference + inode^.blocks.maxDIndirect := inode^.blocks.maxDIndirect + (NUM_EXTENSION_BLOCKS shl superBlock^.blockShift); + end; // while + // XXX ?-> saveBlock(extents^[extentsIndex], indirectExtents); + end; // local function appendToDIndirect() + + function appendToIndirect(newBlockRun:TBlockRun):boolean; + var + extents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + extentsUpperBound, blockIndex, index:uInt32; + tmpBlockRun:TBlockRun; + begin + result := FALSE; // failure by default + // The indirect block is NUM_EXTENSION_BLOCKS long + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun))-1; + + for blockIndex := 0 to NUM_EXTENSION_BLOCKS-1 do + extents[blockIndex] := NIL; // NIL out the array + + if not isValidIAddr(inode^.blocks.indirect) then + begin + // allocate an indirect block + tmpBlockRun := inode^.inodeNum; // alloate near the parent inode + tmpBlockRun.len := NUM_EXTENSION_BLOCKS; // indirect block is NUM_EXTENSION_BLOCKS long + inode^.blocks.indirect := allocBRun(tmpBlockRun); // Try to allocate it + if not isValidIAddr(inode^.blocks.indirect) then + begin + writeln('Failed to allocate indirect block'); + exit + end; + tmpBlockRun := inode^.blocks.indirect; + for blockIndex := 0 to NUM_EXTENSION_BLOCKS-1 do + begin + extents[blockIndex] := cacheBlock(tmpBlockRun); // candidate for cacheBlock() +(* extents[blockIndex] := loadBlock(tmpBlockRun); // candidate for cacheBlock() + fillchar(extents[blockIndex]^, superBlock^.blockSize, 0); + saveBlock(tmpBlockRun, extents[blockIndex]); *) + inc(tmpBlockRun.start); // XXX hopefully this won't wrap around. + end; // for index +// extents := loadBlock(inode^.blocks.indirect); + + inode^.blocks.maxIndirect := inode^.blocks.maxDirect; + end + else + begin + // the indirect block exists, so read it in + tmpBlockRun := inode^.blocks.indirect; + for blockIndex := low(extents) to high(extents) do + begin + extents[blockIndex] := loadBlock(tmpBlockRun); + assert(extents[blockIndex] <> NIL); + inc(tmpBlockRun.start); + end; // for i + end; + // extents[] has the indirect block in it + // We first check to make sure that we're not going to be moving + // to the next section of the dataStream + if (extents[high(extents)]^[extentsUpperBound].len <> 0) then + result := appendToDIndirect(newBlockRun) // promote the newBlockRun to the DIndirect section + else + begin + // scan through for the first free slot + blockIndex := 0; + index := 0; + while (extents[blockIndex]^[index].len <> 0) do + begin + if (index = extentsUpperBound) then + begin + index := 0; + if (blockIndex = high(extents)) then break else inc(blockIndex); + end + else + inc(index); + end; // while +(* for blockIndex := 0 to NUM_EXTENSION_BLOCKS-1 do + for index := 0 to extentsUpperBound do + if extents[blockIndex]^[index].len = 0 then break; *) + extents[blockIndex]^[index] := newBlockRun; // index points to the first free slot. Fill it in with the newBlockRun + tmpBlockRun := inode^.blocks.indirect; + inc(tmpBlockRun.start, blockIndex); // hopefully this won't wrap around + saveBlock(tmpBlockRun, extents[blockIndex]); // save out the indirect block. Could check for failure here + // update the size the maxDoubleIndirect blocks reference + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; // success + end; + end; // local function appendToIndirect() + + function appendToDirect(newBlockRun:TBlockRun):boolean; + var index:uInt32; + begin + with newBlockRun do + writeln('in appendToDirect(); blockRun = ', ag, ':', start, ':', len); + result := FALSE; + with inode^, blocks do + begin + if (direct[high(direct)].len <> 0) then + result := appendToIndirect(newBlockRun) + else + begin + for index := low(direct) to high(direct) do + if (direct[index].len = 0) then break; + direct[index] := newBlockRun; + maxDirect := maxDirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end; // else + end; // with + end; // local function appendToDirect + + function appendToDataStream(newBlockRun:TBlockRun):boolean; + begin + if not(inode^.blocks.maxDIndirect = 0) then result := appendToDIndirect(newBlockRun) + else if not(inode^.blocks.maxIndirect = 0) then result := appendToIndirect(newBlockRun) + else result := appendToDirect(newBlockRun); + end; // local function appendToDataStream + + function continueIndirect(newBlockRun:TBlockRun):boolean; + var + extents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + extentsUpperBound, blockIndex, index:uInt32; + tmpBlockRun:TBlockRun; + begin + result := FALSE; + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun))-1; + + tmpBlockRun := inode^.blocks.indirect; + for blockIndex := low(extents) to high(extents) do + begin + extents[blockIndex] := loadBlock(tmpBlockRun); + assert(extents[blockIndex] <> NIL); + inc(tmpBlockRun.start); // hopefully this won't wrap + end; + + for blockIndex := high(extents) downto low(extents) do + if (extents[blockIndex]^[0].len <> 0) then break; + + for index := extentsUpperBound downto 0 do + if (extents[blockIndex]^[index].len <> 0) then break; + + assert(extents[blockIndex]^[index].len <> 0); // just to be sure + // extents[blockIndex]^[index] has the last entry + if (uInt32(extents[blockIndex]^[index].len) + newBlockRun.len < 65535) then + begin + inc(extents[blockIndex]^[index].len, newBlockRun.len); + tmpBlockRun := inode^.blocks.indirect; + inc(tmpBlockRun.start, blockIndex); + saveBlock(tmpBlockRun, extents[blockIndex]); + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end + else // oops, we overflowed the length + begin + // need to check to see if we were the last indirect entry. If we were + // then we need to allocate a double indirect block and store it in there + if (index = extentsUpperBound) then + begin + if (blockIndex = high(extents)) then + result := appendToDIndirect(newBlockRun) + else + begin + inc(blockIndex); + extents[blockIndex]^[0] := newBlockRun; + tmpBlockRun := inode^.blocks.indirect; + inc(tmpBlockRun.start, blockIndex); + saveBlock(tmpBlockRun, extents[blockIndex]); + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end + end + else + begin + extents[blockIndex]^[index+1] := newBlockRun; + tmpBlockRun := inode^.blocks.indirect; + inc(tmpBlockRun.start, blockIndex); + saveBlock(tmpBlockRun, extents[blockIndex]); + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end; // else + end; // else + + end; // local function continueIndirect + + function continueDirect(newBlockRun:TBlockRun):boolean; + var + index:uInt32; + begin + result := FALSE; + with inode^, blocks do + begin + for index := high(direct) downto low(direct) do + if (direct[index].len <> 0) then break; + + assert(direct[index].len <> 0); // just to be sure + // make sure we don't overflow the length + if (uInt32(direct[index].len) + newBlockRun.len < 65535) then + begin + inc(direct[index].len, newBlockRun.len); // update the last entry + maxDirect := maxDirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end + else // oops, we overflowed the length + begin + // need to check to see if we were the last direct entry. If we were + // then we need to allocate an indirect block and store it in there + if (index = high(direct)) then + result := appendToIndirect(newBlockRun) + else + begin + direct[index+1] := newBlockRun; // store it in the next available entry + maxDirect := maxDirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end; + end; // else + end; // with + end; // local function continueDirect + + function continueDataStream(newBlockRun:TBlockRun):boolean; + begin + // The extended blockRun continues a previous run. + + // When extending the file we request an allocation of more blocks + // near to the last blockRun of the file. If the new blocks + // are contiguous to the last blockRun of the file + // then we can simply extend that blockRun. This not only + // prevents fragmentation when possible, it also reduces the + // number of actual blockRuns needed to store the file. + if (inode^.blocks.maxDIndirect > 0) then result := appendToDIndirect(newBlockRun) + else if (inode^.blocks.maxIndirect > 0) then result := continueIndirect(newBlockRun) + else result := continueDirect(newBlockRun); + end; // local function continueDataStream + +var + blockRun, exBlockRun:TBlockRun; + blocksToAllocate:uInt32; + +begin // extendFile() + // XXX ToDo: + // Need to verify support for indirect and double-indirect blocks + + // Notes: + // 2005-04-27 mji + // This function is called by writeDataStream() when we're about to write + // past the end of the file. When extending out a file, it is almost never + // extended by the amount written. Instead it is at least 1 block, and usually + // more (in our case: 4, as mentioned below). Currently we pass in the number + // of blocks to extend the file by. This is problematic for a few reasons. + // The first is that the code to figure out how much to extend the file by + // would have to be in writeDataStream(). That's not necessarily a problem, + // except that the amount to be extended by depends on many factors, some + // including whether we're writing out more than the default small block + // extension size (currently is 4) or if we're in double indirect blocks, + // which extends out by NUM_DINDIRECT_BLOCKS (also currently 4). + // The second problem is that we save the inode here -- and also in + // writeDataStream() -- because the new size of the file is updated in wDS() + // instead of here. + // The third problem is when you seek past the end of the file and then + // write. Space should be allocated for all the space between the old + // EOF and the new write position. This is common behaviour for most + // filesystems, and should probably be emulated here as well. + // The proper solution is to pass in the new size and let this function + // work out exactly how much space on disk it is really going to take. + + result := FALSE; // failure by default + // basic sanity checks + if (inode = NIL) or (superBlock = NIL) then exit; + + // if the file size is 0 then this is the first extension. + if (inode^.blocks.size = 0) then // first extension + begin + // A file's data is stored in the next allocation group from it's parent + // inode. This will help distribute the data across the disk. + + // Find out how many blocks the new size will take up. Do this by rounding + // up the size to the nearest block. Then we round that up to the nearest + // NUM_DIRECT_BLOCKS multiple. + blocksToAllocate := uInt32(((newSize+(superBlock^.blockSize-1)) shr superBlock^.blockShift)); + blocksToAllocate := (blocksToAllocate + (NUM_EXTENSION_BLOCKS-1)) and not (NUM_EXTENSION_BLOCKS-1); + // Allocate directory info in the same AG as the inode + if (inode^.mode and S_IFDIR <> 0) then + setBlockRun(exBlockRun, inode^.inodeNum.AG, 0, blocksToAllocate) + else + setBlockRun(exBlockRun, nextAG(inode^.inodeNum.AG), 0, blocksToAllocate); + exBlockRun := allocBRun(exBlockRun); // try to allocate a blockrun + assert(exBlockRun.len <> 0); + if (exBlockRun.len = 0) then exit; // erp. Couldn't extend the file + inode^.blocks.direct[0] := exBlockRun; // Set the first direct run + // The space allocated for the file can and may be different than the + // actual size. Set the maxDirect to the size (in bytes) of how much space + // we allocated + inode^.blocks.maxDirect := blocksToAllocate shl superBlock^.blockShift; + end // size = 0 + else + begin + // the size of the file wasn't 0 (handled above). We need to extend the + // file out. Find where the last blocks of the file are + + // Note: + // 2005-04-28 mji + // We're going to have a potential problem here. When we check to see how many blocks we + // want to extend by, we don't know for sure if we're actually going to end up in the + // dataStream section we were assuming. For example, we may have no more room in the + // indirect block section of dataStream and thus will have to store the entry in the double + // indirect area. If the constants for each section are different, then we may be requesting the + // wrong amount. This is *only* a problem if any of the constants are different. Therefore + // the solution is to pick some arbitrary extension amount (probably 4 or 8 blocks) and stick + // with that. If we pick the amount that will end up in the double indirect area (currently 4 + // but I'm considering 8) then we can easily break apart our blockRun if we're writing it + // to the double indirect section. + // IMPORTANT NOTE + // Once this value is chosen it will be set in stone since we will break everything by + // changing it. Math done for retrieving the blockRun from the double indirect region will + // rely on this value, and it can never be changed. BeFS uses 4 blocks for the double + // indirect section, and that seems fine with me. The largest file using contiguous blockRuns + // will be 38GB. I am, however, pondering 8 blocks instead. Remember, extensions are done + // as a multiple of this value. + // 2005-04-28 mji + // I decided to use a single constant, NUM_EXTENSION_BLOCKS, and it is set to 4. + blocksToAllocate := uInt32((newSize+(superBlock^.blockSize-1)) shr superBlock^.blockShift); + // blocksToAllocate is now rounded up to the nearest block of how much space we need. + blocksToAllocate := (blocksToAllocate + (NUM_EXTENSION_BLOCKS-1)) and not (NUM_EXTENSION_BLOCKS-1); + // blocksToAllocate is now rounded up to the nearest multiple of NUM_EXTENSION_BLOCKS + // now subtract off how many blocks we already have + dec(blocksToAllocate, inode^.blocks.blockCount); + + // XXX + // I don't think this is right. We need to find the last block returned by + // getFileSpace(), not the size. Something to ponder/look into/test + //blockRun := findBRun(@inode^.blocks, inode^.blocks.size shr superBlock^.blockShift); + assert(inode^.blocks.blockCount <> 0); // if blockCount is 0, we're in big trouble + blockRun := findBRun(@inode^.blocks, inode^.blocks.blockCount-1); + assert(blockRun.len <> 0); + if (blockRun.len = 0) then exit; // This would be odd + exBlockRun := blockRun; // copy the last run to exBlockRun + exBlockRun.len := blocksToAllocate; // set the requested number of blocks + exBlockRun := allocBRun(exBlockRun); // allocate a block run near the previous one + assert(exBlockRun.len <> 0); + if (exBlockRun.len = 0) then exit; // should probably yell louder about this + + // blockRun points to the last blockRun structure associated with this inode + if (exBlockRun.start = blockRun.start+1) and (exBlockRun.AG = blockRun.AG) then + continueDataStream(exBlockRun) + else + appendToDataStream(exBlockRun); + end; // else size <> 0 + inode^.blocks.size := newSize; // Now we update the size here and not in writeDataStream() + inc(inode^.blocks.blockCount, blocksToAllocate); // update the blockCount in the inode dataStream + // no matter where we stored the extended blockRun, we have updated the inode + + assert(saveBlock(inode^.inodeNum, inode)); // 0 result means success + result := TRUE; // no error +end; // TUbixFS.extendFile + +function TUbixFS.findBRun; +var + position:int64; +begin + setBlockRun(result, 0, 0, 0); + if (dataStream = NIL) then exit; + if (blockNum >= dataStream^.blockCount) then + begin + writeln('Requested block outside the block count in the dataStream'); + exit + end; + + position := int64(blockNum) shl superBlock^.blockShift; + if (position < dataStream^.maxDirect) then + result := findBRunDirect(dataStream, blockNum) + else if (position < dataStream^.maxIndirect) then + result := findBRunIndirect(dataStream, blockNum) + else if (position < dataStream^.maxDIndirect) then + result := findBRunDIndirect(dataStream, blockNum) + else writeln('Block position is not mapped by dataStream'); +end; // TUbixFS.findBRun + +function TUbixFS.findBRunDirect; +var + sum, i, offset:uInt32; +begin + // set the result to a nil blockrun + setBlockRun(result, 0, 0, 0); + + sum := 0; + with dataStream^ do + for i := low(direct) to high(direct) do + with direct[i] do + begin + if (blockNum < sum + len) then // found it + begin + offset := blockNum - sum; + setBlockRun(result, AG, start + offset, len - offset); + exit + end; + inc(sum, len); + end; // for/with +end; // TUbixFS.findBRunDirect + +function TUbixFS.findBRunIndirect; +var + extents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + sum, offset, blockIndex, index, extentsUpperBound:uInt32; + tmpBlockRun:TBlockRun; +begin + // NOTE: Basic error checking on whether superblock <> NIL isn't done here. + setBlockRun(result, 0, 0, 0); + + // load the indirect block + tmpBlockRun := dataStream^.indirect; + for blockIndex := low(extents) to high(extents) do + begin + extents[blockIndex] := loadBlock(tmpBlockRun); + assert(extents[blockIndex] <> NIL); + inc(tmpBlockRun.start); // hope this doesn't wrap around + end; + + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun))-1; + // We either need to adjust sum forward or blockNum back. + // sum := (dataStream^.maxDirect+superBlock^.blockSize-1) shr superBlock^.blockShift; + + // We need to decrease blockNum by the amount of blocks the direct portion took up. + // This can be calculated by dividing the maxDirect byte size by the block size. + + blockNum := blockNum - dword(dataStream^.maxDirect shr superBlock^.blockShift); + sum := 0; + for blockIndex := low(extents) to high(extents) do + for index := 0 to extentsUpperBound do + with extents[blockIndex]^[index] do + begin + if (blockNum < sum + len) then // found it + begin + offset := blockNum - sum; + setBlockRun(result, AG, start + offset, len - offset); + exit // was a break + end; + inc(sum, len); + end; // for/with +end; // TUbixFS.findBRunIndirect + +const printStuff:boolean = FALSE; + +function TUbixFS.findBRunDIndirect; +var + extents:^array[0..0] of TBlockRun; + sum, blockIndex, indirectIndex, index, offset:uInt32; + blocksPerIndirect:uInt32; // per indirect section (NUM_EXTENSION_BLOCKS * superBlock^.blockSize) === 4 x 1k block + blocksPerDIndirect:uInt32; // per single double indirect block === 1k block + runsPerBlock:uInt32; // runs per single block (blockSize div sizeof(TBlockRun)) + tmpBlockRun:TBlockRun; +begin + // NOTE: Basic error checking on whether superblock <> NIL isn't done here. + setBlockRun(result, 0, 0, 0); + + // find the indirect block that contains the block run we're looking for. + // This is done with some clever shifting + + // first adjust the blockNum, taking into account how many blocks are used + // by the direct and indirect sections + blockNum := blockNum - dword(dataStream^.maxIndirect shr superBlock^.blockShift); + // Each indirect block holds NUM_EXTENSION_BLOCKS*((superBlock^.blockSize*NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun)) blocks + // e.g. + // NUM_EXTENSION_BLOCKS = 4 + // blockSize = 1024*NUM_EXTENSION_BLOCKS + // sizeof(TBlockRun) = 8 + // blocks referenced per indirect block = 4 * (4096 / 8) === 2048 + // indirect block index = (block we're looking for) / (blocks referenced per indirect block) + // blockrun index in indirect block = ((block we're looking for) % (blocks referenced per indirect block)) div NUM_EXTENSION_BLOCK + + // If we're really *really* clever, we will only need to load two blocks to find this blockrun. + // So let's begin the cleverness. + tmpBlockRun := dataStream^.doubleIndirect; + + runsPerBlock := superBlock^.blockSize div sizeof(TBlockRun); + + blocksPerIndirect := NUM_EXTENSION_BLOCKS * ((superBlock^.blockSize * NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun)); + + blocksPerDIndirect := blocksPerIndirect * runsPerBlock; + + assert(blocksPerIndirect <> 0); // since this would div by 0 below anyway + assert(blocksPerDIndirect <> 0); + + blockIndex := blockNum div blocksPerDIndirect; + + index := (blockNum mod blocksPerDIndirect) div blocksPerIndirect; + + assert(blockIndex < NUM_EXTENSION_BLOCKS); // this would mean we're way outside the range of possible addressable blocks + inc(tmpBlockRun.start, blockIndex); + extents := loadBlock(tmpBlockRun); + assert(extents <> NIL); + + tmpBlockRun := extents^[index]; + + // now find the proper indirect sub-block + + blockIndex := (blockNum mod blocksPerIndirect) div (NUM_EXTENSION_BLOCKS*runsPerBlock); + + assert(blockIndex < NUM_EXTENSION_BLOCKS); + + // adjust the double indirect block address + inc(tmpBlockRun.start, blockIndex); + extents := loadBlock(tmpBlockRun); + assert(extents <> NIL); + + // now find which indirect run references the block we're looking for + index := (blockNum mod (runsPerBlock*NUM_EXTENSION_BLOCKS)) div NUM_EXTENSION_BLOCKS; +(* + blockIndex := (index mod blocksPerIndirect) div (superBlock^.blockSize div sizeof(TBlockRun)); + assert(blockIndex < NUM_EXTENSION_BLOCKS); + + index := (blockNum mod blocksPerIndirect) div NUM_EXTENSION_BLOCKS; + + tmpBlockRun := extents[ + i := blockNum div (NUM_EXTENSION_BLOCKS * ((superBlock^.blockSize*NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun))); + // i should now hold which entry in the double indirect block we need to load + assert(i < ((superBlock^.blockSize*NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun))); + + // load the proper indirect block beneath the double indirect block + extents := loadBlock(extents^[i]); + assert(extents <> NIL); + + // now seek into this indirect block by taking the modulus of the number + // of blocks referenced by an indirect block + i := (blockNum mod (NUM_EXTENSION_BLOCKS * ((superBlock^.blockSize*NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun)))) div NUM_EXTENSION_BLOCKS; +*) + with extents^[index] do + begin + // extents^[i] is a block extent inside an indirect block which + // was referenced by the double indirect block. Now we need to see + // which block inside this extent we wanted. We do that by taking the + // modulus of the requested block number by the number of blocks + // referenced in each blockrun. BeFS used 4 per; and as of writing + // this comment that is what I'm using. + offset := blockNum and (NUM_EXTENSION_BLOCKS-1); + setBlockRun(result, AG, start + offset, len - offset); + end; // with + +end; // TUbixFS.findBRunDIndirect + +function TUbixFS.freeUsedBlocks; +var + FBL:PBTree; + tmpBlockRun, leftBlockRun, rightBlockRun:TBlockRun; + searchRec:BTreeSearchRec; + mergeLeft, mergeRight:boolean; + + u:uPtr; +begin + result := -1; // failure by default + // basic sanity checks + if (superBlock = NIL) or (blockRun.len = 0) then exit; + if not isValidIAddr(superBlock^.BAT) then exit; + +with blockRun do writeln('---- freeing used blocks: ', AG, ':', start, ':', len); + new(FBL, open(iAddrToStr(superBlock^.BAT), new(PUbixBTreeVFS, init(@self)))); +// new(FBL, open('BAT.DAT', NIL)); + if (FBL = NIL) then exit; + FBL^.InstallUserFunctions(compareKeyBlockRun, copyKeyBlockRun, keySizeBlockRun); + + fillchar(u, sizeof(u), 0); // not sure what to put in here yet + + assert(FBL^.Insert(@blockRun, u)); // put in our block run + if not FBL^.FindFirst(@blockRun, searchRec) then begin FBL^.PrintWholeTree('after.txt'); runError; end; + + //assert(FBL^.FindFirst(@blockRun, searchRec)); // yes, we look it right back up + if FBL^.FindPrev(searchRec) then + begin + assert(searchRec.key <> NIL); + leftBlockRun := TBlockRun(searchRec.key^); + mergeLeft := (leftBlockRun.len <> 0) and (leftBlockRun.start + leftBlockRun.len = blockRun.start); + end + else mergeLeft := FALSE; + + assert(FBL^.FindFirst(@blockRun, searchRec)); // We're looking this one up a lot + + if FBL^.FindNext(searchRec) then + begin + assert(searchRec.key <> NIL); + rightBlockRun := TBlockRun(searchRec.key^); + mergeRight := (rightBlockRun.len <> 0) and (blockRun.start + blockRun.len = rightBlockRun.start) + end + else mergeRight := FALSE; + + if (mergeLeft or mergeRight) then + begin + FBL^.Delete(@blockRun, u); // testing [this is to prevent that blockrun from being used in the event the BAT grows] + if mergeLeft then assert(FBL^.Find(@leftBlockRun, u)); + if mergeLeft then FBL^.Delete(@leftBlockRun, u); + + if mergeRight then assert(FBL^.Find(@rightBlockRun, u)); + if mergeRight then FBL^.Delete(@rightBlockRun, u); + + // this blockRun merges two other free spaces into a single unified space + tmpBlockRun := blockRun; + if mergeLeft then + begin + dec(tmpBlockRun.start, leftBlockRun.len); + inc(tmpBlockRun.len, leftBlockRun.len); + end; + if mergeRight then inc(tmpBlockRun.len, rightBlockRun.len); + + // tmpBlockRun now holds the unified run. Delete the appropiate ones +// FBL^.Delete(@blockRun, u); + assert(FBL^.Insert(@tmpBlockRun, u)); + end; + + superBlock^.usedBlocks := superBlock^.usedBlocks - int64(blockRun.len); + FBL^.FindClose(searchRec); + dispose(FBL, done); + result := 0; // success +end; // TUbixFS.freeUsedBlocks +(* +function TUbixFS.freeUsedBlocks; +var + FBL:int8ArrayPtr; + FBLSize:uInt32; + startByte, endByte:uInt32; + bitCount, bitOffset:uInt32; + startBlock:uInt32; + byteOffset:uInt32; +begin + // Notes: + // 2005-04-27 mji + // freeUsedBlocks() will clear bits in the BAT that are associated + // with the blockRun passed in. To do this we load only the portion + // of the BAT we're going to modify. (As a side note, readDataStream() + // will load a minimum of one block anyway, so this may not really be + // much of an optimization). + + // XXX + // We *might* want to invalidate blocks in the cache that are marked as + // free. So far it seems to work fine without, but that might change. + + result := -1; + // basic sanity checks + if ((superBlock = NIL) or (blockRun.len = 0)) then exit; + +with blockRun do writeln('---- freeing used blocks: ', AG, ':', start, ':', len); + // Compute how much of the BAT we have to read in. + with blockRun, superBlock^ do + begin + startBlock := (AG shl AGShift) + start; + startByte := startBlock shr 3; // divide by 8 since it's a bitfield + endByte := (startBlock + len) shr 3; // divide by 8 since it's a bitfield + FBLSize := endByte - startByte + 1; + + GetMem(FBL, FBLSize); + assert(readDataStream(BAT, startByte, FBL, FBLSize) = 0); + bitOffset := 7-(startBlock and 7); + byteOffset := 0; + + for bitCount := 1 to len do + begin + FBL^[byteOffset] := FBL^[byteOffset] and not (1 shl bitOffset); + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + end; // for bitCount + // save back out the modified block + assert(writeDataStream(BAT, startByte, FBL, FBLSize) = 0); + + FreeMem(FBL, FBLSize); + usedBlocks := usedBlocks - len; + end; // with + + result := 0; +end; // TUbixFS.freeUsedBlocks +*) +function TUbixFS.getFileSize; +begin + result := 0; + if (inode = NIL) then exit; + result := inode^.blocks.size; +end; // TUbixFS.getFileSize + +function TUbixFS.getFileSpace; +begin + // getFileSpace() will return the size of the file's allocated space + result := 0; + if (inode = NIL) or (superBlock = NIL) then exit; + result := inode^.blocks.blockCount shl superBlock^.blockShift; +end; // TUbixFS.getFileSpace + +function TUbixFS.iAddrToStr; +var s:string; +begin + // Note: this won't work in C/C++ because strings are really null + // terminated character arrays. So, the B^Tree won't hold a string + // with nulls in it properly + length(s) := sizeof(iAddr); + move(iAddr, s[1], sizeof(iAddr)); + result := s; +end; // TUbixFS.iAddrToStr; + +function TUbixFS.loadBlock; +var + blockAddress:int64; + cacheNode:PCacheNode; + u:uPtr; + searchRec:BTreeSearchRec; +begin + result := NIL; // failure by default; + // 2005-04-29 mji + // I'm not really sure how much error checking to put in here. Chances are that + // if we're calling this function everything is fine. For now, with such limited + // use I'll keep it pretty light. I think that this will eventually be used for + // dealing with the block cache, so more checking might be required later. + if (dev = NIL) or (superBlock = NIL) or (blockCache = NIL) then exit; + + with blockAddr, superBlock^ do + blockAddress := (AG shl AGShift) + start; + + fillchar(u, sizeof(u), 0); // just to be safe + cacheNode := NIL; + // XXX ToDo + // Fix this so that if there is no cache, we only have one pointer + // Check for a full cache + + if (blockCache^.Find(@blockAddress, u)) then + begin + // block was in the cache. + assert(u.p <> NIL); + cacheNode := u.p; + assert(cacheNode^.data <> NIL); + // If we're going to honour the locked flag in the cacheNode, then + // we should put a mutex here + + // Now we need to update the blockTime. We do this by first deleting + // the cacheNode out of the blockTime cache. + blockTime^.Delete(@cacheNode^.lastUsed, u); // Delete old one + cacheNode^.lastUsed := getRDTSCTime(); // Get the new timestamp + blockTime^.Insert(@cacheNode^.lastUsed, u); // Insert with new time + result := cacheNode^.data; // return the data pointer + end + else + begin + // Requested block isn't in cache + + // XXX ToDo: + // Check to see if we've hit the maximum cache entry limit. If so, we + // need to drop off the least recently used (look for the first key + // in blockTime) and possibly save it to disk if it has been modified. + + if (blockCache^.getKeyCount() = maxCacheEntry) then + begin + blockTime^.getFirstKey(searchRec); // least recently used block + cacheNode := searchRec.value.p; // get the pointer from the value + assert(cacheNode <> NIL); + // I suppose we could write a syncBlock() function, since this + // code is also duplicated in sync(). + if (cacheNode^.dirty) then + begin + with cacheNode^ do + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + cacheNode^.dirty := FALSE; + end; // if dirty + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + blockTime^.Delete(@cacheNode^.lastUsed, u); + // We've deleted the cacheNode out of both the blockCache and + // the blockTime trees. We're going to re-use the cacheNode. + cacheNode^.address := blockAddress; + cacheNode^.lastUsed := getRDTSCTime(); + // No need to update data, dataSize, or dirty. We re-use the first two, + // and dirty will be set to FALSE above if necessary. Might need to + // reset locked if we're using that. + end + else + new(cacheNode, init(blockAddress, getRDTSCTime(), superBlock^.blockSize)); + + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + u.p := cacheNode; + if not (blockCache^.Insert(@blockAddress, u)) then + writeln('Error inserting ', blockAddress, ' into blockCache in loadBlock()'); + if not (blockTime^.Insert(@cacheNode^.lastUsed, u)) then + writeln('Error inserting into blockTime'); + // now we need to fill the data from the device + with superBlock^ do + dev^.read(cacheNode^.data, + blockAddress * (blockSize shr SECTOR_SHIFT), + blockSize shr SECTOR_SHIFT + ); // dev^.read + result := cacheNode^.data; + end; +end; // TUbixFS.loadBlock + +function TUbixFS.markUsedBlocks; +begin +end; // TUbixFS.markUsedBlocks +(* +function TUbixFS.markUsedBlocks; +var + FBL:uInt8ArrayPtr; + FBLSize:uInt32; + startByte, endByte:uInt32; + bitCount, bitOffset:uInt32; + startBlock:uInt32; + byteOffset:uInt32; +begin + + result := -1; + // basic sanity checks + if ((superBlock = NIL) or (blockRun.len = 0)) then exit; + +with blockRun do writeln('---- marking used blocks: ', AG, ':', start, ':', len); + + // Compute how much of the BAT we have to read in. + with blockRun, superBlock^ do + begin + startBlock := (AG shl AGShift) + start; + startByte := startBlock shr 3; + endByte := (startBlock + len) shr 3; + FBLSize := endByte - startByte + 1; + GetMem(FBL, FBLSize); + fillchar(FBL^, FBLSize, 0); // better safe than sorry + assert(readDataStream(BAT, startByte, FBL, FBLSize) = 0); + + bitOffset := 7-(startBlock and 7); + byteOffset := 0; + for bitCount := 1 to len do + begin + FBL^[byteOffset] := FBL^[byteOffset] or (1 shl bitOffset); + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + end; // for bitCount + assert(writeDataStream(BAT, startByte, FBL, FBLSize) = 0); + + FreeMem(FBL, FBLSize); + + usedBlocks := usedBlocks + len; + end; // with + result := 0; +end; // TUbixFS.markUsedBlocks +*) +function TUbixFS.nextAG; +begin + // there's no error we can throw from here if superblock is NIL, + // so don't bother to check. Just hope whomever calls this + // function has done some basic sanity checks + result := (AG+1) mod superBlock^.numAGs +end; // TUbixFS.nextAG + +function TUbixFS.pathToIAddr; + + function recursePath(path:PChar; len:integer; iAddr:inodeAddr):inodeAddr; + var + dirTree:PBTree; + u:uPtr; + nameSize:integer; + pfs:PUbixBTreeVFS; + begin + // if the string length is 0 then we have found the inode in the previous + // recurse, so return what we found. + if (len = 0) then + begin + result := iAddr; + exit; + end + else + setIAddr(result, 0, 0, 0); + // hm. If we're looking for something like ``/.'' then the ``/'' is stripped + // off in the main call leaving only ``.'', which means that this malformed + // path check would trigger. Same with /foo/bar if bar is a dir. + //if uInt8ArrayPtr(path)^[0] = ord('/') then exit; // malformed path + if not (isValidIAddr(iAddr)) then exit; // sanity check + nameSize := 0; + while (len > 0) and (uInt8ArrayPtr(path)^[nameSize] <> ord('/')) do + begin + inc(nameSize); + dec(len); + end; + uInt8ArrayPtr(path)^[nameSize+1] := 0; + if (len <> 0) then dec(len); + new(dirTree, open(IAddrToStr(iAddr), new(PUbixBTreeVFS, init(@self)))); + if (dirTree = NIL) then exit; // hm.. what happens to the PUbixBTreeVFS is this is NIL? + // note that the dirTree is a tree of BT_PCHAR, and searches are case sensitive. + // if the tree doesn't exist, Find() should return false. + if dirTree^.Find(path, u) then result := recursePath(path + nameSize + 1, len, u.iAddr); + dispose(dirTree, done); + end; // recursePath + +var + len:integer; + copyOfPath:PChar; +begin + setIAddr(result, 0, 0, 0); + + if (path = NIL) or (superBlock = NIL) then exit; + + len := strlen(path); + if (len = 0) then exit; // empty string + if uInt8ArrayPtr(path)^[0] <> ord('/') then exit; // malformed path + // we need to make a copy of the path since recursePath() will munge it + GetMem(copyOfPath, len+1); // Allocate some memory for the path + assert(copyOfPath <> NIL); + move(path^, copyOfPath^, len+1); // copy it + result := recursePath(copyOfPath+1, len-1, superBlock^.rootDir); + freemem(copyOfPath, len+1); +end; // TUbixFS.pathToIAddr + +procedure TUbixFS.printSuperBlock; +begin + if (superBlock = NIL) then exit; + with superBlock^ do + begin + writeln('superBlock^.name........... ', name); + writeln('superBlock^.magic1......... ', hex(magic1)); + writeln('superBlock^.fsByteOrder.... ', fsByteOrder); + writeln('superBlock^.blockSize...... ', blockSize); + writeln('superBlock^.blockShift..... ', blockShift); + writeln('superBlock^.numBlocks...... ', numBlocks); + writeln('superBlock^.usedBlocks..... ', usedBlocks); + writeln('superBlock^.magic2......... ', hex(magic2)); + writeln('superBlock^.blocksPerAG.... ', blocksPerAG); + writeln('superBlock^.AGShift........ ', AGShift); + writeln('superBlock^.numAGs......... ', numAGs); + writeln('superBlock^.flags.......... ', hex(flags)); + writeln('superBlock^.magic3......... ', hex(magic3)); + with superBlock^.BAT do + writeln('superBlock^.BAT............ ', AG, ':', start, ':', len); + with superBlock^.rootDir do + writeln('superBlock^.rootDir........ ', AG, ':', start, ':', len); + end; +end; // TUbixFS.printSuperBlock + +function TUbixFS.saveBlock; +var + blockAddress:int64; + cacheNode:PCacheNode; + u:uPtr; + searchRec:BTreeSearchRec; +begin + + result := FALSE; // failure by default; + if (blockCache = NIL) or (blockTime = NIL) or (dev = NIL) or (superBlock = NIL) then exit; + with blockAddr, superBlock^ do + blockAddress := (AG shl AGShift) + start; + cacheNode := NIL; + fillchar(u, sizeof(u), 0); // best be safe + // first check to see if the blockAddress is in the cache + if (blockCache^.Find(@blockAddress, u)) then + begin + // block was in cache + cacheNode := u.p; + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + if (cacheNode^.data <> buf) then + begin + // The buffer with the data we want to save was not the same as the one + // in the cache. This means that it was not obtained with loadBlock(). + // We need to copy that pointer to our block. + move(buf^, cacheNode^.data^, cacheNode^.dataSize); + end; + // Now we need to update the blockTime. We do this by first deleting + // the cacheNode out of the blockTime cache. + blockTime^.Delete(@cacheNode^.lastUsed, u); // Delete old one + cacheNode^.lastUsed := getRDTSCTime(); // Get the new timestamp + blockTime^.Insert(@cacheNode^.lastUsed, u); // Insert with new time + end + else + begin + // block was not in cache + if (blockCache^.getKeyCount() = maxCacheEntry) then + begin + blockTime^.getFirstKey(searchRec); + cacheNode := searchRec.value.p; + assert(cacheNode <> NIL); + // I suppose we could write a syncBlock() function, since this + // code is also duplicated in sync(). + if (cacheNode^.dirty) then + begin + with cacheNode^ do + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + cacheNode^.dirty := FALSE; + end; // if dirty + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + blockTime^.Delete(@cacheNode^.lastUsed, u); + // We've deleted the cacheNode out of both the blockCache and + // the blockTime trees. We're going to re-use the cacheNode. + cacheNode^.address := blockAddress; + cacheNode^.lastUsed := getRDTSCTime(); + // No need to update data, dataSize, or dirty. We re-use the first two, + // and dirty will be set to FALSE above if necessary. Might need to + // reset locked if we're using that. + end + else + new(cacheNode, init(blockAddress, getRDTSCTime(), superBlock^.blockSize)); + + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + move(buf^, cacheNode^.data^, cacheNode^.dataSize); + u.p := cacheNode; + if not (blockCache^.Insert(@blockAddress, u)) then + writeln('Error inserting ', blockAddress, ' into blockCache in saveBlock()'); + if not (blockTime^.Insert(@cacheNode^.lastUsed, u)) then + writeln('Error inserting into blockTime'); + end; + cacheNode^.dirty := TRUE; // indicate the block has been modified + result := TRUE; +end; // TUbixFS.saveBlock + +function TUbixFS.isBAT; +begin + result := FALSE; + if (superBlock = NIL) then exit; + with superBlock^.BAT do + result := (AG = iAddr.AG) and (start = iAddr.start); +end; // TUbixFS.isBAT + +function TUbixFS.isValidIAddr; +begin + result := (iAddr.len <> 0) and (iAddr.AG < superBlock^.numAGs); +end; // TUbixFS.isValidIAddr + +function TUbixFS.readDataStream; +var + tmpBlock:uInt8ArrayPtr; + blockRun:TBlockRun; + size, remainder:uInt32; + blockStart:uInt32; + blocksToRead:uInt32; + bytesToRead:uInt32; + inode:PUbixFSInode; +begin +// with iAddr do +// writeln('readDataStream(', AG, ':', start, ':', len, ', ', position, ', ', hex(dword(buf)), ', ', length, ');'); + result := -1; + if ((buf = NIL) or (superBlock = NIL)) then exit; + inode := loadBlock(iAddr); + tmpBlock := NIL; + size := length; + remainder := uInt32(position mod superBlock^.blockSize); + + if (remainder <> 0) then + begin + // the position we've been asked to read from doesn't align to + // a block size. We need to read in the previous block and pad + // the data + blockStart := uInt32(position shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + bytesToRead := min(size, (superBlock^.blockSize - remainder)); + move(tmpBlock^[remainder], buf^, bytesToRead); + inc(buf, bytesToRead); + dec(size, bytesToRead); + position := position + bytesToRead; + end; // if remainder != 0 + + // If the blockSize is always a power of 2, then you can eliminate the MOD and use + // an AND operation instead, e.g. remainder := size and (superBlock^.blockSize-1) + remainder := size mod superBlock^.blockSize; + dec(size, remainder); + + // Size is currently in bytes. The remainder variable above holds the number + // of remaining bytes (if any) outside full blocks we have to write. + + // convert the size into blocks + size := size shr superBlock^.blockShift; + + while (size > 0) do + begin + blockRun := findBRun(@inode^.blocks, dword(position shr superBlock^.blockShift)); + assert(blockRun.len > 0); + for blocksToRead := 0 to min(blockRun.len, size)-1 do + begin + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + move(tmpBlock^, buf^, superBlock^.blockSize); + dec(size); + inc(buf, superBlock^.blockSize); + inc(blockRun.start); // This might wrap around? + dec(blockRun.len); // might be unnecessary + end; // for + end; // while + + if (remainder <> 0) then + begin + // There is less than a block of data left to read. + blockStart := dword((position+length-remainder) shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + if (blockRun.len = 0) then + begin + printStuff := TRUE; + blockRun := findBRun(@inode^.blocks, uInt32(position shr superBlock^.blockShift)); + end; + + assert(blockRun.len > 0); + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + move(tmpBlock^, buf^, remainder); + end; + + result := 0; +end; // TUbixFS.readDataStream + +procedure TUbixFS.setBlockRun; +begin + blockRun.AG := AG; + blockRun.start := start; + blockRun.len := len; +end; // TUbixFS.setBlockRun + +procedure TUbixFS.setIAddr; +begin + inode.AG := AG; + inode.start := start; + inode.len := len; +end; // TUbixFS.setIAddr + +function TUbixFS.strToIAddr; +begin + // Note: this won't work in C/C++ because strings are really null + // terminated character arrays. So, the B^Tree won't hold a string + // with nulls in it properly + // begin fiendishly clever hack + move(s[1], result, sizeof(inodeAddr)); + // end fiendishly clever hack +end; // TUbixFS.strToIAddr + +function TUbixFS.shrinkFile; + + function shrinkDirect(var trimBlockCount:uInt32):boolean; + var + index:uInt32; + blockRun:TBlockRun; // for freeUsedBlocks() + begin + result := FALSE; + with inode^, blocks do + begin + dec(blockCount, trimBlockCount); // this is a little presumptious + maxDirect := maxDirect - (trimBlockCount shl superBlock^.blockShift); // so is this + for index := high(direct) downto low(direct) do + if (direct[index].len <> 0) then break; // find the last used one + assert(direct[index].len <> 0); + while (trimBlockCount <> 0) do + begin + if (direct[index].len >= trimBlockCount) then + begin + blockRun := direct[index]; // make a copy + dec(direct[index].len, trimBlockCount); // subtract from the len + if (direct[index].len = 0) then // check to see if that was the whole run + setBlockRun(direct[index], 0, 0, 0) // yup, mark this entry as blank + else // else, nope .. update blockRun for freeUsedBlocks() + setBlockRun(blockRun, blockRun.AG, blockRun.start + direct[index].len, blockRun.len - direct[index].len); + freeUsedBlocks(blockRun); // free the blockRun we just trimmed + trimBlockCount := 0; // all done + break; // end the while + end + else + begin + dec(trimBlockCount, direct[index].len); // subtract the length of this run from the total trim amount + freeUsedBlocks(direct[index]); // free this blockRun + setBlockRun(direct[index], 0, 0, 0); // mark this entry as blank + end; + // keep going until we've hit bottom or while trimBlockCount <> 0 + if (index = low(direct)) then break else dec(index); + end; // while + end; // with + assert(trimBlockCount = 0); // Better be 0. + result := TRUE; + end; // local function shrinkDirect + + function shrinkIndirect(var trimBlockCount:uInt32):boolean; + var + extents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + extentsUpperBound, blockIndex, index:uInt32; + tmpBlockRun:TBlockRun; + begin + result := FALSE; // failure by default; + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun))-1; + + if not isValidIaddr(inode^.blocks.indirect) then + begin + writeln('For some reason you called shrinkIndirect() when there was no valid indirect block.'); + exit; + end; + + // load the indirect block + tmpBlockRun := inode^.blocks.indirect; + for blockIndex := low(extents) to high(extents) do + begin + extents[blockIndex] := loadBlock(tmpBlockRun); + assert(extents[blockIndex] <> NIL); + inc(tmpBlockRun.start); // hope this doesn't wrap around + end; // for blockIndex + + for blockIndex := high(extents) downto low(extents) do + if (extents[blockIndex]^[0].len <> 0) then break; + + for index := extentsUpperBound downto 0 do + if (extents[blockIndex]^[index].len <> 0) then break; + assert(extents[blockIndex]^[index].len <> 0); // this would be odd + + with inode^, blocks do + begin + while (trimBlockCount <> 0) do + begin + if (extents[blockIndex]^[index].len >= trimBlockCount) then + begin + tmpBlockRun := extents[blockIndex]^[index]; // make a copy + dec(extents[blockIndex]^[index].len, trimBlockCount); // subtract from the len + maxIndirect := maxIndirect - (trimBlockCount shl superBlock^.blockShift); // update inode^.blocks.maxIndirect + dec(blockCount, trimBlockCount); // update inode^.blocks.blockCount + if (extents[blockIndex]^[index].len = 0) then // check to see if that was the whole run + begin + // Reclaim this entry. But, if the index is 0, then + // we've trimmed away the indirect section completely. + if (index = 0) then + begin + if (blockIndex = 0) then // toast the indirect block + begin + freeUsedBlocks(indirect); + setIAddr(indirect, 0, 0, 0); + end + else // just move to the previous section of the indirect block + begin + // XXX we probably need to save out extents[blockIndex] right here + dec(blockIndex); + index := extentsUpperBound; + end; + end + else // else just clear this entry + setBlockRun(extents[blockIndex]^[index], 0, 0, 0) // yup it was the whole run, mark this entry as blank + end + else // else, nope .. update tmpBlockRun for freeUsedBlocks() + setBlockRun(tmpBlockRun, tmpBlockRun.AG, tmpBlockRun.start + extents[blockIndex]^[index].len, tmpBlockRun.len - extents[blockIndex]^[index].len); + freeUsedBlocks(tmpBlockRun); // free the blockRun we just trimmed + trimBlockCount := 0; // all done + break; // end the while + end + else + begin + dec(trimBlockCount, extents[blockIndex]^[index].len); // subtract the length of this run from the total trim amount + freeUsedBlocks(extents[blockIndex]^[index]); // free this blockRun + setBlockRun(extents[blockIndex]^[index], 0, 0, 0); // mark this entry as blank + // This branch means that trimBlockCount is not 0 yet (relevant to code below) + end; + // keep going until we've hit bottom or while trimBlockCount <> 0 + if (index = 0) then + begin + if (blockIndex = 0) then + begin + // If we're here, then the indirect block is no longer needed + // and we need to scan through the direct section. Toast the + // indirect block + freeUsedBlocks(indirect); + setBlockRun(indirect, 0, 0, 0); + result := shrinkDirect(trimBlockCount); + exit; + end + else + begin + dec(blockIndex); + index := extentsUpperBound; + end; + end + else + dec(index); + end; // while + // XXX + // This next piece of code should go away and the proper pieces should be saved above. + // Since we have caching at the block level, we can go ahead and save blocks when they + // change without worrying too much about speed. + if isValidIAddr(indirect) then // we might have removed the indirect section altogether + begin + tmpBlockRun := indirect; + for blockIndex := low(extents) to high(extents) do + begin + assert(saveBlock(tmpBlockRun, extents[blockIndex])); + inc(tmpBlockRun.start); // hope this doesn't wrap around + end; + end; // if isValidIAddr(indirect) + end; // with + result := TRUE; // success + end; // local function shrinkIndirect + + function shrinkDIndirect(var trimBlockCount:uInt32):boolean; + var + extents:^array[0..0] of TBlockRun; + indirectExtents:^array[0..0] of TBlockRun; + extentsIndex, indirectExtentsIndex:uInt32; + extentsUpperBound:uInt32; + tmpBlockRun:TBlockRun; + begin + result := FALSE; // failure by default + + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun)) - 1; + assert(isValidIAddr(inode^.blocks.doubleIndirect)); + extents := loadBlock(inode^.blocks.doubleIndirect); + assert(extents <> NIL); + for extentsIndex := extentsUPperBound downto 0 do + if (extents^[extentsIndex].len <> 0) then break; + assert(extents^[extentsIndex].len <> 0); + indirectExtents := loadBlock(extents^[extentsIndex]); + assert(indirectExtents <> NIL); + for indirectExtentsIndex := extentsUpperBound downto 0 do + if (indirectExtents^[indirectExtentsIndex].len <> 0) then break; + assert(indirectExtents^[indirectExtentsIndex].len <> 0); + while (trimBlockCount >= NUM_EXTENSION_BLOCKS) do + begin + dec(trimBlockCount, indirectExtents^[indirectExtentsIndex].len); + dec(inode^.blocks.blockCount, indirectExtents^[indirectExtentsIndex].len); + with inode^, blocks do + maxDIndirect := maxDIndirect - (indirectExtents^[indirectExtentsIndex].len shl superBlock^.blockShift); + freeUsedBlocks(indirectExtents^[indirectExtentsIndex]); // free the blockRun + if (indirectExtentsIndex = 0) then + begin + freeUsedBlocks(extents^[extentsIndex]); // free this indirect block + if (extentsIndex = 0) then + begin + freeUsedBlocks(inode^.blocks.doubleIndirect); // free the double indirect block + setBlockRun(inode^.blocks.doubleIndirect, 0, 0, 0); + // XXX + // We should put a check to make sure inode^.blocks.maxDIndirect is equal to maxIndirect + with inode^, blocks do + assert(maxDIndirect = maxIndirect); + inode^.blocks.maxDIndirect := 0; + result := shrinkIndirect(trimBlockCount); + exit; + end + else + begin + dec(extentsIndex); + assert(saveBlock(inode^.blocks.doubleIndirect, extents)); // XXX this isn't right + indirectExtents := loadBlock(extents^[extentsIndex]); + assert(indirectExtents <> NIL); + indirectExtentsIndex := extentsUpperBound; + end; // else + end + else + dec(indirectExtentsIndex); + end; // while + result := TRUE; // success + end; // local function shrinkDIndirect + + function shrinkDataStream(var trimBlocks:uInt32):boolean; + begin + if (inode^.blocks.maxDIndirect > 0) then result := shrinkDIndirect(trimBlocks) else + if (inode^.blocks.maxIndirect > 0) then result := shrinkIndirect(trimBlocks) + else result := shrinkDirect(trimBlocks); + end; // local function shrinkDataStream +var + blocksToTrim:uInt32; + +begin // shrinkFile() + // XXX ToDo + // Figure out which pieces of the datastream need to be saved + // in shrinkIndirect() and shrinkDIndirect() + + result := FALSE; // failure by default + if (inode = NIL) or (superBlock = NIL) then exit; + + // start with the original block count + blocksToTrim := inode^.blocks.blockCount; + // Subtract off how much space the file actually takes up. + dec(blocksToTrim, uInt32((inode^.blocks.size+(superBlock^.blockSize-1)) shr superBlock^.blockShift)); + + // Do a quick check to see if we're in double indirect blocks. If we are, and the amount to trim + // is less than NUM_EXTENSION_BLOCKS then we don't actually need to do anything. + // Also do a quick check to see if blocksToTrim is 0 .. which means we really don't have to do anything. + if (blocksToTrim = 0) or (isValidIAddr(inode^.blocks.doubleIndirect) and (blocksToTrim < NUM_EXTENSION_BLOCKS)) then + begin + result := TRUE; // Technically there was no failure + exit; + end; + result := shrinkDataStream(blocksToTrim); + assert(saveBlock(inode^.inodeNum, inode)); +end; // TUbixFS.shrinkFile + +procedure TUbixFS.sync; +var + searchRec:BTreeSearchRec; +begin + // XXX ToDo: + // Need to mark the superblock as clean. + + if (blockCache = NIL) or (dev = NIL) then exit; + // We sync out the blocks in ascending order from the blockCache. + // To do this we examine each entry to see if the block is dirty. + // If it is, we write it out to the device and set dirty to FALSE. + if (blockCache^.getFirstKey(searchRec)) then + repeat + syncBlock(searchRec.value.p); + until not blockCache^.FindNext(searchRec); + // Also write out the superblock. + dev^.write(superBlock, 1, 1); +end; // TUbixFS.sync + +function TUbixFS.syncBlock; +begin + result := FALSE; + if (dev = NIL) or (cacheNode = NIL) then exit; + with cacheNode^ do + if (dirty) then + begin + assert(data <> NIL); + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + dirty := FALSE; + end; // if dirty + result := TRUE; +end; // TUbixFS.syncBlock +{$IFDEF GRAPHICS} + {$I graphics.pas} +{$ENDIF} + +function TUbixFS.vfs_close; +var + u:uPtr; + vnid:PVnid; + inode:PUbixfsInode; +begin + result := -1; + if (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + if not (vnids^.Find(@fileDescriptors^[handle].iAddr, u)) then writeln('Could not find iAddr in vnids'); + vnid := u.p; + if (vnid = NIL) then + begin + writeln('vnid is NIL'); + exit; + end; + + with vnid^ do + begin + dec(refCount); + if (refCount = 0) then + begin + // refCount has fallen to 0. If the file is deleted we should take care of it here + inode := loadBlock(fileDescriptors^[handle].iAddr); + // if file is deleted then inode^.blocks.size = 0; + assert(inode <> NIL); + // shrink the file + assert(shrinkFile(inode)); + vnids^.Delete(@inode^.inodeNum, u); + setIAddr(fileDescriptors^[handle].iAddr, 0, 0, 0); + end; + end; + if not vNodeQueue^.enqueue(handle) then writeln('Error enqueuing handle back into vNodeQueue'); + result := 0; +end; // TUbixFS.vfs_close + +function TUbixFS.vfs_fCreate; +var + inode:PUbixFSInode; + dirTree:PBTree; + dirIAddr, fileIAddr:inodeAddr; + u:uPtr; + searchRec:BTreeSearchRec; + len : integer; + path:PChar; + fileNamePtr:PChar; + curPos:integer; + vnid:PVnid; + handle:integer; +begin + result := -1; // error by default + if (filename = NIL) or (superBlock = NIL) then exit; + // we need to split the filename into it's component path and filename pieces. + // From there we need to see if the file exists. If it does then I guess we + // should truncate the file and load the inode. Otherwise allocate a new inode + len := strlen(filename); + if (len = 0) then exit; + if uInt8ArrayPtr(filename)^[0] <> ord('/') then exit; // malformed path + GetMem(path, len+1); + strCopy(path, filename); + curPos := len; + + setIAddr(dirIAddr, 0, 0, 0); // zero out the inode address for the directory + setIAddr(fileIAddr, 0, 0, 0); // zero out the inode address for the file + + while (curPos > 0) and (uInt8ArrayPtr(path)^[curPos] <> ord('/')) do dec(curPos); + fileNamePtr := path + curPos + 1; + if (curPos = 0) then + begin + // file is in the root dir + writeln('path: /'); + writeln('filename: ', fileNamePtr); + dirIAddr := pathToIAddr('/'); + end + else + begin + uInt8ArrayPtr(path)^[curPos] := 0; // null out the separator + writeln('Path: ', path); + writeln('Filename: ', fileNamePtr); + dirIAddr := pathToIAddr(path); + end; + + with dirIAddr do + writeln('iaddr to path: ', AG, ':', start, ':', len); + assert(isValidIAddr(dirIAddr)); + if not isValidIAddr(dirIAddr) then exit; + // should probably check to make sure that we're opening a directory and not a file + new(dirTree, open(IAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self)))); + if (dirTree = NIL) then exit; + // We need to ascertain if the file is existing or new. + // If the file exists, we should truncate the file + // If the file is new, we need to create a new inode and write the entry to the directory + // Then we need to allocate a file descriptor and store it somewhere. + if dirTree^.Find(fileNamePtr, u) then + begin + writeln(fileNamePtr, ' already exists'); + // XXX ToDo: + // Set the filesize to 0 and call shrinkFile on the inode. + // So, uhm, what happens if the file is already open? + end + else + begin + writeln('creating new file: ', fileNamePtr); + writeln('allocating new inode'); + inode := allocInode(dirIAddr); // alloc a new inode (pass in the parent iaddr) + move(fileNamePtr^, inode^.name, strlen(filenamePtr)); + assert(inode <> NIL); // make sure it isn't nil + if (inode = NIL) then exit; + assert(saveBlock(inode^.inodeNum, inode)); // save the new inode + u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr + // now add the file to the directory + writeln('Inserting ', fileNamePtr, ' into directory returned ', dirTree^.Insert(fileNamePtr, u)); + end; + dispose(dirTree, done); + FreeMem(path, len+1); + + // This file doesn't have an associated vnid. Create a new one. + new(vnid); + vnid^.refCount := 1; + vnid^.iAddr := inode^.inodeNum; + vnid^.mode := 0; // XXX ? + u.p := vnid; + if not (vnids^.insert(@inode^.inodeNum, u)) then writeln('Could not insert into vnids'); + + if not vNodeQueue^.dequeue(handle) then + begin + writeln('Out of file descriptors!'); + exit; + end; + + with fileDescriptors^[handle] do + begin + TID := 0; // ThreadID + iAddr := inode^.inodeNum; + position := 0; + flags := 0; // XXX ? + end; + result := handle; +end; // TUbixFS.vfs_fCreate + +function TUbixFS.vfs_fExist; +begin + // XXX ToDo: + // find out why fExist('/./.') fails + // fExist('/.') no longer fails + + result := -1; + if ((superBlock = NIL) or (path = NIL)) then exit; + if (not isValidIAddr(superBlock^.rootDir)) then exit; + if isValidIAddr(pathToIAddr(path)) then result := 0; +end; // TUbixFS.vfs_fExist + +function TUbixFS.vfs_format; +var + sector:array[0..511] of byte; + fs:PUbixFS; + logicalBlocks, fsBATSize, fsBATBlocks:uInt32; + sb:PdiskSuperBlock; + inode:PubixfsInode; + i, fsBlockShift, lBlockShift, nodeSize, keyLength:integer; + blockRun:TBlockRun; + FBL:PBTree; + remainderBlocks:uInt32; + u:uPtr; + blockBit:shortint; +begin + result := -1; + if (device = NIL) then exit; + if (fsBlockSize < 1024) or (fsBlockSize > 8192) then + begin + writeln('Blocksize must be between 1024 and 8192'); + exit; + end; + // zero out the sector + fillchar(sector, sizeof(sector), 0); + + writeln('device^.sectors: ', device^.sectors); + + if not quick then + begin + system.write('clearing device... '); + + for i := 0 to device^.sectors-1 do + device^.write(@sector, i, 1); + + writeln('done'); + end; // if !quick + + + fsBlockShift := 10; {minimum is 1024} + while (1 shl fsBlockShift < fsBlockSize) do inc(fsBlockShift); + + // allocate a new superBlock and clear it + + new(sb); + if (sb = NIL) then exit; + fillchar(sb^, sizeof(TDiskSuperBlock), 0); + + // device^.sectors is the number of 512 byte sectors + + lBlockShift := fsBlockSize shr 9; + writeln('lBlockShift: ', lBlockShift); + logicalBlocks := device^.sectors div lBlockShift; // logical blocks + writeln('logical blocks: ', logicalBlocks); + + with sb^ do + begin + strcopy(name, 'UbixFS'); + magic1 := UBIXFS_MAGIC_1; + fsByteOrder := 0; + blockSize := fsBlockSize; + blockShift := fsBlockShift; + numBlocks := logicalBlocks; + usedBlocks := 32; // XXX Based on what we allocate below for the new BAT system! + inodeSize := fsBlockSize; + magic2 := UBIXFS_MAGIC_2; + blocksPerAG := min(65536, blockSize shl 3); + AGShift := 10; // realistically it should be a min of 13 + while (1 shl AGShift <> blocksPerAG) do inc(AGShift); + numAGs := (dword(numBlocks)+(blocksPerAG-1)) div blocksPerAG; + + flags := UBIXFS_CLEAN; + setBlockRun(logBlocks, 0, 0, 0); + logStart := 0; + logEnd := 0; + magic3 := UBIXFS_MAGIC_3; + setIAddr(indicies, 0, 0, 0); + setIAddr(rootDir, 0, 0, 0); + end; // with + // create the free block list inode + + getmem(inode, fsBlockSize); // make the inode the same size as a block + if (inode = NIL) then exit; + + fillchar(inode^, sb^.inodeSize, 0); + + with inode^ do + begin + magic := UBIXFS_INODE_MAGIC; + + // inodes point to themselves + setIAddr(inodeNum, 0, sb^.logBlocks.len+1, 1); // the +1 for the start might be wrong + setIAddr(parent, 0, 0, 0); + + // If the partition is larger than 550GB we will need to allocate more than + // one blockRun for the BAT. + +//oldBAT -- fsBATSize := logicalBlocks shr 3; // compute how many bytes the BAT takes up +//oldBAT -- fsBATBlocks := (fsBATSize + fsBlockSize-1) div fsBlockSize; +//oldBAT -- writeln('fsBATSize: ', fsBATSize); +//oldBAT -- writeln('fsBATBlocks: ', fsBATBlocks); +//oldBAT -- setBlockRun(blocks.direct[0], 0, inodeNum.start+1, fsBATBlocks); + +//oldBAT -- blocks.size := (fsBATBlocks * fsBlockSize); // round the file size up to the nearest block size +//oldBAT -- blocks.maxDirect := blocks.size-1; +//oldBAT -- blocks.blockCount := fsBATBlocks; + blocks.size := 0; + blocks.blockcount := 30; // manually allocate blocks + blocks.maxDirect := (blocks.blockCount shl fsBlockShift)-1; // set the size of the file + setBlockRun(blocks.direct[0], 0, inodeNum.start+1, blocks.blockCount); // manually allocate blocks + + uid := 0; + gid := 0; + mode := 0; + strcopy(name, 'Block Allocation Tree'); + flags := INODE_LOGGED; + + setIAddr(attributes, 0, 0, 0); + inodeSize := sb^.inodeSize; + + end; {with} + sb^.BAT := inode^.inodeNum; + writeln('writing out superblock to sector 1'); + device^.write(sb, 1, 1); + // note that the superblock does not contain proper inode addresses for the + // BAT and rootDir. Those are sync'd to disk in the destructor after they've been + // created below. + + system.write('Writing out BAT inode... '); + // write out the Block Allocation Table inode + with inode^.inodeNum, sb^ do + device^.write(inode, + ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT), + inodeSize shr SECTOR_SHIFT + ); // device^.write + writeln('done'); + // Okay, the superblock is the only piece of information needed + // to mount an UbixFS partition + new(fs, init(device)); + assert(fs <> NIL); + assert(fs^.vfs_init() = 0); + + // The freshly formatted parition is now mounted. + // From here we need to create: + // BAT (Block Allocation Table) file contents + // journal + // root dir + new(FBL, init(iAddrToStr(sb^.BAT), BAT_PAGE_SIZE, BT_CUSTOM, new(PUbixBTreeVFS, init(fs)))); +// new(FBL, init('BAT.DAT', BAT_PAGE_SIZE, BT_CUSTOM, NIL)); + if (FBL = NIL) then + begin + writeln('Error creating Block Allocation Tree in vfs_format()'); + halt; + end; + + FBL^.InstallUserFunctions(compareKeyBlockRun, copyKeyBlockRun, keySizeBlockRun); + + fillchar(u, sizeof(u), 0); + + setBlockRun(blockRun, 0, 0, 0); // 0th AG tag + assert(FBL^.Insert(@blockRun, u)); // 0th AG marker +writeln(dword(sb^.numBlocks)); +writeln(sb^.numAGs); + remainderBlocks := dword(sb^.numBlocks-sb^.usedBlocks); + i := inode^.blocks.direct[0].start+inode^.blocks.blockCount; + setBlockRun(blockRun, 0, i, min(sb^.blocksPerAG-i, remainderBlocks)); + dec(remainderBlocks, blockRun.len); +with blockRun do + writeln('0th AG free block section: ', AG, ':', start, ':', len); + + FBL^.Insert(@blockRun, u); // 0th AG free block list + for i := 1 to sb^.numAGs-1 do + begin + setBlockRun(blockRun, i, 0, 0); // AG tags + assert(FBL^.Insert(@blockRun, u)); + blockRun.start := 1; + blockRun.len := min(remainderBlocks, sb^.blocksPerAG-1); + assert(FBL^.Insert(@blockRun, u)); + dec(remainderBlocks, min(remainderBlocks, sb^.blocksPerAG)); + sb^.usedBlocks := sb^.usedBlocks + 1; + end; // for i +writeln('remainderBlocks: ', remainderBlocks); +assert(remainderblocks = 0); + dispose(FBL, done); + (* + // inode still points to the BAT. Clear out the file in case people decided to + // use quick formatting + for i := 0 to (dword(inode^.blocks.size) div sizeof(sector))-1 do + fs^.writeDataStream(sb^.BAT, i*sizeof(sector), @sector, sizeof(sector)); + setBlockRun(blockRun, 0, 0, 1); + fs^.markusedBlocks(blockRun); // mark superBlock block as used + // mark the journal as used here + fs^.markUsedBlocks(sb^.BAT); // mark the BAT inode as used + // now mark the BAT file blocks as used. Note that if the BAT takes up + // more than one blockRun (unlikely, but not impossible) then this code + // will have to check for that. + writeln('BAT inode blocks.direct[0].len: ', inode^.blocks.direct[0].len); + fs^.markUsedBlocks(inode^.blocks.direct[0]); + + // If the number of blocksPerAG doesn't divide evenly into the + // number of logical blocks, mark out all the "remainder" blocks + // We need to mark the blocks as used in the BAT manually because + // if we do it through markUsedBlocks() it will change the usedBlocks + // count. + + remainderBlocks := (fsBATBlocks shl sb^.blockShift) - fsBATSize; + writeln('RemainderBlocks: ', remainderBlocks); + if (remainderBlocks <> 0) then + begin + // check if the remainder blocks are evenly divisible by 8 + if (remainderBlocks and 7 <> 0) then + begin + blockBit := uInt8(1 shl (remainderBlocks and 7)) -1; + fs^.writeDataStream(sb^.BAT, fsBatSize, @blockBit, 1); + blockBit := -1; + for i := fsBatSize+1 to (fsBATBlocks shl sb^.blockShift)-1 do + fs^.writeDataStream(sb^.BAT, i, @blockBit, 1); + end + else + begin + blockBit := -1; + for i := fsBatSize to (fsBATBlocks shl sb^.blockShift)-1 do + fs^.writeDataStream(sb^.BAT, i, @blockBit, 1); + end; + end; +*) + fs^.vfs_mkdir('/'); + if (fs^.vfs_fExist('/.') = -1) then + writeln('Warning! /. doesn''t exist') + else + writeln('fExist(''/.'') returned properly'); + if (fs^.vfs_fExist('/nofile.dat') <> -1) then + writeln('Warning! /nofile.dat exists?') + else + writeln('fExist(''/nofile.dat'') returned properly'); + + if (fs^.vfs_fExist('/nodir/nofile.dat') <> -1) then + writeln('Warning! /nodir/nofile.dat exists?') + else + writeln('fExist(''/nodir/nofile.dat'') returned properly'); + +// writeln('fs^.vfs_fExist(''/.'') returned: ', fs^.vfs_fExist('/.')); + writeln('fs^.vfs_fExist(''/nofile.dat'') returned: ', fs^.vfs_fExist('/nofile.dat')); + fs^.printSuperBlock(); + + dispose(fs, done); + writeln('Format complete!'); + dispose(sb); + freemem(inode, fsBlockSize); + +end; // TUbixfS.vfs_format + +function TUbixFS.vfs_init; +var index:uInt32; +begin + // This function should probably not be called more than once without + // calling the appropiate shutdown code + writeln('TUbixFS.vfs_init()'); + result := -1; + if (dev = NIL) then exit; + new(superBlock); + assert(superBlock <> NIL); + if (superBlock = NIL) then exit; + fillchar(superBlock^, sizeof(superBlock^), 0); // better safe than sorry + // read in the superBlock. It's always the 1st block on the partition (counting from 0) + system.write('reading in superBlock... '); + dev^.read(superBlock, 1, 1); + writeln('done'); + + assert(superBlock^.magic1 = UBIXFS_MAGIC_1); + assert(superBlock^.magic2 = UBIXFS_MAGIC_2); + assert(superBlock^.magic3 = UBIXFS_MAGIC_3); + assert(strcomp(superBlock^.name, 'UbixFS') = 0); + assert((1 shl superBlock^.blockShift) = superBlock^.blockSize); + assert((1 shl superBlock^.AGShift) = superBlock^.blocksPerAG); + assert(superBlock^.flags = UBIXFS_CLEAN); + + // create the file decriptor tree + + + // The blockCache tree has a [key::value] pair of [block address::pointer to cacheNode]. + // The blockTime tree has a [key::value] pair of [last used time::pointer to cacheNode]. + + // create the block cache tree + new(blockCache, init('', 256, BT_INT64, NIL)); + // create the block last used time tree + // The last used time is obtained through the RDTSC instruction to give us a unique + // and increasing timestamp. When the cache is full we drop off the least recently + // used block. Because the tree is sorted, this will be the first entry. + new(blockTime, init('', 256, BT_INT64, NIL)); + + // Create the file descriptors and related stuff + + // vnids is a tree which has a [key::value] pair of [iAddr::pointer to vNode]. + new(vnids, init('', 256, BT_INT64, NIL)); + + // vnodeQueue is a queue which has a list of available FD handles + new(vNodeQueue, init(maxFDEntry, sizeof(uInt32))); + // fill the vNodeQueue with a list of all available FD handles + for index := 0 to maxFDEntry-1 do + vNodeQueue^.enqueue(index); + + // Allocate and clear the file descriptor array + GetMem(fileDescriptors, maxFDEntry*sizeof(TVNode)); + fillchar(fileDescriptors^, maxFDEntry*sizeof(TVNode), 0); + + result := 0; // success +end; // TUbixFS.vfs_init + +function TUbixFS.vfs_mkdir; +const + dot:array[0..1] of char = '.'#0; + dotdot:array[0..2] of char = '..'#0; +var + inode:PUbixFSInode; + dirIAddr:inodeAddr; + iAddr:inodeAddr; + bRun:TBlockRun; + len:integer; + dirTree:PbTree; + u:uPtr; + pathToParentDir:PChar; + dirNamePtr:PChar; + curPos:integer; + +begin + result := -1; + assert(path <> NIL); + assert(superBlock <> NIL); + if (path = NIL) or (superBlock = NIL) then exit; + + len := strlen(path); + assert(len <> 0); + if (len = 0) then exit; + + if (len = 1) and (uInt8ArrayPtr(path)^[0] = ord('/')) then + begin + writeln('Creating root directory'); + // wanting to create the root. Check to see if the root dir inode is empty + assert(not isValidIAddr(superBlock^.rootDir)); + if isValidIAddr(superBlock^.rootDir) then exit; // should set a proper error code here + setIAddr(iAddr, 0, 0, 0); // technically the "parent" iaddr + inode := allocInode(iAddr); // allocate an inode (parent points to la-la land) + assert(inode <> NIL); // make sure it isn't nil + if (inode = NIL) then exit; + inode^.mode := S_IFDIR; + inode^.flags := INODE_LOGGED; + superBlock^.rootDir := inode^.inodeNum; // assign the root dir + strcopy(inode^.name, '/'); // XXX Needs to be an attribute + assert(saveBlock(superBlock^.rootDir, inode)); // save the inode + u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr + + // open the root dir + with superBlock^.rootDir do + writeln('Root dir inode: ', AG, ':', start, ':', len); + writeln('Opening root dir tree'); + new(dirTree, init(iAddrToStr(superBlock^.rootDir), 1024, BT_PCHAR, new(PUbixBTreeVFS, init(@self)))); + writeln('Inserting ''.'''); + // Get ready to insert the '.' :: inode^.inodeNum key :: value pair + writeln('dirTree^.Insert(@dot, u) returned ', dirTree^.Insert(@dot, u)); // '.' points to ourself + writeln('dirTree^.Insert(@dotDot, u) returned ', dirTree^.Insert(@dotDot, u)); // '..' points to ourself, too + dispose(dirTree, done); // close and save the directory + end + else + begin + // subdir support goes here + setIAddr(dirIAddr, 0, 0, 0); + len := strlen(path); + if (len = 0) then exit; + if uInt8ArrayPtr(path)^[0] <> ord('/') then exit; // malformed path + GetMem(pathToParentDir, len+1); + strCopy(pathToParentDir, path); + curPos := len; + while (curPos > 0) and (uInt8ArrayPtr(pathToParentDir)^[curPos] <> ord('/')) do dec(curPos); + dirNamePtr := pathToParentDir + curPos + 1; + if (curPos = 0) then + begin + writeln('New directory being created below root'); + writeln('dir name: ', dirNamePtr); + dirIAddr := superBlock^.rootDir; + end + else + begin + if (curPos <> 0) then uInt8ArrayPtr(pathToParentDir)^[curPos] := 0; // null out the last / separator + writeln('Path to parent dir: ', pathToParentDir); + writeln('dir name: ', dirNamePtr); + dirIAddr := pathToIAddr(pathToParentDir); + + end; + inode := allocInode(dirIAddr); // allocate an inode + assert(inode <> NIL); + if (inode = NIL) then exit; // set some appropiate error code (not sure what) + inode^.mode := S_IFDIR; + strcopy(inode^.name, dirNamePtr); + assert(saveBlock(inode^.inodeNum, inode)); + u.iAddr := inode^.inodeNum; + // open the parent dir + new(dirTree, open(iAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self)))); + writeln('dirTree^.Insert(@inode^.name, u) returned ', dirTree^.Insert(@inode^.name, u)); + dispose(dirTree, done); + // now open the subdir and insert '.' and '..' + new(dirTree, init(iAddrToStr(inode^.inodeNum), 1024, BT_PCHAR, new(PUbixBTreeVFS, init(@self)))); + // Get ready to insert the '.' :: inode^.inodeNum key :: value pair + writeln('dirTree^.Insert(@dot, u) returned ', dirTree^.Insert(@dot, u)); // '.' points to ourself + u.iAddr := dirIAddr; + writeln('dirTree^.Insert(@dotdot, u) returned ', dirTree^.Insert(@dotdot, u)); // '..' points to parent dir + dispose(dirTree, done); + FreeMem(pathToParentDir, len+1); + end; // else subdir + + result := 0; +end; // TUbixFS.vfs_mkdir + +function TUbixFS.vfs_read; +var + inode:PUbixFSInode; + u:uPtr; +begin + result := -1; + if (buffer = NIL) or (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + with fileDescriptors^[handle] do + begin + assert(readDataStream(iAddr, position, buffer, size) = 0); + position := position + int64(size); + result := size; + end; // with +end; // TUbixFS.vfs_read + +function TUbixFS.vfs_seek; +begin + result := -1; + if (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + fileDescriptors^[handle].position := newPosition; + result := 0; +end; // TUbixFS.vfs_seek + +function TUbixFS.vfs_unlink; +var + dirTree:PbTree; + iAddr:inodeAddr; + dirIAddr:inodeAddr; + path, fileNamePtr:PChar; + curPos:integer; + len:integer; + inode:PUbixfsInode; + u:uPtr; + vnid:PVnid; +begin + // XXX ToDo: + // Set appropiate error codes. + // If the "file" is actually a directory, bail on that + // If bailing out, don't forget to reclaim allocated memory + // replace inode^.name with the actual attribute + result := -1; + if (fileName = NIL) or (superBlock = NIL) or (vnids = NIL) then exit; + + // We need to break the path apart into a directory path and filename + len := strlen(filename); + if (len = 0) then exit; + getmem(path, len+1); + strcopy(path, filename); + curPos := len; + while (curPos > 0) and (uInt8ArrayPtr(path)^[curPos] <> ord('/')) do + dec(curPos); + fileNamePtr := path + curPos +1; + + if (curPos = 0) then + begin + dirIAddr := superBlock^.rootDir; + end + else + begin + uInt8ArrayPtr(path)^[curPos] := 0; + dirIAddr := pathToIAddr(path); + end; + // XXX We can't exit because we have allocated memory to clean up + if not (isValidIAddr(dirIAddr)) then exit; // set some error code here + + new(dirTree, open(IAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self)))); + assert(dirTree <> NIL); + fillchar(u, sizeof(u), 0); + if dirTree^.Find(fileNamePtr, u) then + begin + inode := loadBlock(u.iAddr); + if (inode = NIL) then + begin + writeln('error loading inode in unlink()'); + exit; // XXX cannot exit from here either + end; + dirTree^.Delete(@inode^.name, u); + // We know the file exists.. but does anybody have it open? + if vnids^.Find(@inode^.inodeNum, u) then + begin + // file was already open + // mark it for deletion later + inode^.flags := inode^.flags OR INODE_DELETED; + saveBlock(inode^.inodeNum, inode); + end + else + begin + // Tis okay to delete the file + inode^.blocks.size := 0; + shrinkFile(inode); + freeUsedBlocks(inode^.inodeNum); + end; + end + else + writeln('File does not exist'); + dispose(dirTree, done); + + FreeMem(path, len+1); + result := 0; +end; // TUbixFS.vfs_unlink + +function TUbixFS.vfs_write; +begin + result := -1; + if (buffer = NIL) or (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + with fileDescriptors^[handle] do + begin + assert(writeDataStream(iAddr, position, buffer, size) = 0); + position := position + int64(size); + result := size; + end; // with +end; // TUbixFS.vfs_write + + +function TUbixFS.writeDataStream; +var + tmpBlock:uInt8ArrayPtr; + blockRun:TBlockRun; + size, remainder:uInt32; + blockStart:uInt32; + blocksToWrite:uInt32; + bytesToWrite:uInt32; + inodeChanged:boolean; + inode:PUbixFSInode; +begin +// with iAddr do +// writeln('writeDataStream(', AG, ':', start, ':', len, ', ', position, ', ', hex(dword(buf)), ', ', length, ');'); + + result := -1; + if ((buf = NIL) or (superBlock = NIL)) then exit; + inode := loadBlock(iAddr); + tmpBlock := NIL; + + inodeChanged := FALSE; + if (position + length > getFileSpace(inode)) then + begin + extendFile(inode, position+length); +// inode^.blocks.size := position+length; // update the blocks + writeln('after extendfile: size = ', inode^.blocks.size, ' getFileSpace() = ', getFileSpace(inode)); + + end + else + if (position + length > inode^.blocks.size) then + begin + // The above case checks to see if we're writing out past + // the end of the allocated space on the device. If we do, we extend + // out the file. However, if we're just writing past the end of + // the file, we have to update the size. + + inode^.blocks.size := position+length; + // XXX To Do: + // Might want to clear out the blocks we're skipping past, otherwise + // there might be garbage in there + assert(saveBlock(iAddr, inode)); // save the inode + end; + + size := length; + remainder := uInt32(position mod superBlock^.blockSize); + + if (remainder <> 0) then + begin + // the position we've been asked to write to doesn't align to + // a block size. We need to read in the previous block and pad + // the data + blockStart := uInt32(position shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + if (tmpBlock = NIL) then exit; + bytesToWrite := min(size, (superBlock^.blockSize - remainder)); + move(buf^, tmpBlock^[remainder], bytesToWrite); + assert(saveBlock(blockRun, tmpBlock)); + inc(buf, bytesToWrite); + dec(size, bytesToWrite); + position := position + bytesToWrite; + end; // if remainder != 0 + + remainder := size mod superBlock^.blockSize; + dec(size, remainder); + + // Size is currently in bytes. The remainder variable above holds the number + // of remaining bytes (if any) outside full blocks we have to write. + + // convert the size into blocks + size := size shr superBlock^.blockShift; + + while (size > 0) do + begin + blockRun := findBRun(@inode^.blocks, uInt32(position shr superBlock^.blockShift)); + if (blockRun.len = 0) then + begin + printStuff := TRUE; + blockRun := findBRun(@inode^.blocks, uInt32(position shr superBlock^.blockShift)); + end; + assert(blockRun.len > 0); + blocksToWrite := min(blockRun.len, size); + for blocksToWrite := 0 to min(blockRun.len, size)-1 do + begin + assert(saveBlock(blockRun, buf)); + inc(blockRun.start); // This might wrap around? + dec(blockRun.len); // might be unnecessary + dec(size); + inc(buf, superBlock^.blockSize); + end; + end; // while + + if (remainder <> 0) then + begin + // There is less than a block of data left to write. Read in the old block + blockStart := uInt32((position+length-remainder) shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + // obtain a pointer to the block + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + if (tmpBlock = NIL) then exit; + move(buf^, tmpBlock^[0], remainder); + assert(saveBlock(blockRun, tmpBlock)); + end; + result := 0; +end; // TUbixFS.writeDataStream + +destructor TUbixFS.done; +var + searchRec:BTreeSearchRec; + inode:PUbixfsInode; + cacheNode:PCacheNode; + dirtyCount:integer; + u:uPtr; + f:text; + x:integer; +begin + sync(); + // XXX ToDo: + // close all "open" files manually. + if (vnids <> NIL) then dispose(vnids, done); + if (vNodeQueue <> NIL) then dispose(vNodeQueue, done); + if (fileDescriptors <> NIL) then FreeMem(fileDescriptors, maxFDEntry*sizeof(TVNode)); + vnids := NIL; + vNodeQueue := NIL; + fileDescriptors := NIL; + + if (blockTime <> NIL) then dispose(blockTime, done); + if (blockCache <> NIL) then + begin + dirtyCount := 0; + fillchar(u, sizeof(u), 0); +system.assign(f, 'bc1.txt'); +system.rewrite(f); + while (blockCache^.getFirstKey(searchRec)) do + begin + cacheNode := searchRec.value.p; +x := integer(cacheNode^.address); + writeln(f, hex(x)); + + assert(cacheNode <> NIL); + if (cacheNode^.dirty) then inc(dirtyCount); + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + dispose(cacheNode, done); + end; +system.close(f); + if (dirtyCount <> 0) then writeln('Dirty blocks in cache: ', dirtyCount); + dispose(blockCache, done); + end; + if (superBlock <> NIL) then dispose(superBlock); +end; // TUbixFS.done + +procedure TUbixFS.examine; +var + dirTree:PBTree; + searchRec:BTreeSearchRec; + u:uPtr; +begin + new(dirTree, open(iAddrToStr(superBlock^.BAT), new(PUbixBTreeVFS, init(@self)))); + dirTree^.installUserFunctions(compareKeyBlockRun, copyKeyBlockRun, keySizeBlockRun); + dirTree^.printWholeTree('bat.txt'); + dispose(dirTree, done); + writeln('------------- examining FS -----------------'); + write('Opening root dir...'); + new(dirTree, open(IAddrToStr(superblock^.rootDir), new(PUbixBTreeVFS, init(@self)))); + //dirTree^.PrintList('rootDir.txt'); + writeln('done'); + writeln('Root directory has ', dirTree^.getKeyCount(), ' entries'); + dirTree^.getFirstKey(searchRec); + repeat + with searchRec.value.iAddr do + write('value: ', AG, ':', start, ':', len); + writeln(' key: ', pchar(searchRec.key)); + until not dirTree^.findNext(searchRec); + dispose(dirTree, done); + if (blockCache <> NIL) then writeln('Block cache has ', blockCache^.getKeyCount(), ' entries'); + writeln('Space used: ', superBlock^.usedBlocks, '(', superBlock^.numBlocks, ')'); + writeln('------------------ done --------------------'); +end; + +end. \ No newline at end of file diff --git a/bIndex.pas b/bIndex.pas new file mode 100755 index 0000000..9ab07a3 --- /dev/null +++ b/bIndex.pas @@ -0,0 +1,559 @@ +program bIndex; +uses lists, dos, strings, crt, debug; + +type + TFileTypes = (ftUNKNOWN, ftTXT, ftBIN, ftHTML, ftPAS, ftASM, ftINC, ftH, ftI, ftC, ftCPP, ftLOG, ftFW4, ftBTI); + +type + TFileInfo = packed record + fileNameRecordNum : uPtr; + wordList : uPtr; + timeStamp : uPtr; + reserved : uPtr; + end; // TFileInfo + +type + pCharArrayPtr = ^pCharArray; + pCharArray = array[0..0] of char; + +type + tagType = (inTag, noTag); + +type + PIndexer = ^TIndexer; + TIndexer = object + private + masterBTI : PBTree; + fileInfoBTI : PBTree; + fileNameDAT : file; + fileInfoDAT : file; + dataPath : string; + startingPath: string; + curFileCount : integer; + foundFileCount : integer; + procedure deleteWordList(fileInfo:TFileInfo); + function fileType(const filename:string):TFileTypes; + function genUniqueFileName:uInt32; + function getTimeStamp(const filename:string):uInt32; + procedure parseFile(fileName:string; fileInfo:TFileInfo; ftype:TFileTypes); + + public + constructor init; + procedure Index(path, filemask:string); + destructor done; + end; // TIndex + +const spinner : array[0..3] of char = '\|/-'; + +constructor TIndexer.init; +var + direc : DirStr; + fname : NameStr; + exten : ExtStr; +begin + clrscr; + + masterBTI := NIL; + fileInfoBTI := NIL; + + fsplit(paramstr(0), direc, fname, exten); + if (direc = '') then + getDir(0, startingPath) + else + startingPath := direc; + + dataPath := appendPathDelimiter(startingPath) + 'indicies\'; + {$I-} mkdir(dataPath); {$I+} + IOResult(); + + new(masterBTI, init(dataPath+'master.bti', 1024, BT_STRING, NIL)); + new(fileInfoBTI, init(dataPath+'fileInfo.bti', 1024, BT_STRING, NIL)); + + assign(fileInfoDAT, dataPath+'fileInfo.dat'); + if (fsearch(dataPath+'fileInfo.dat', '') = '') then + rewrite(fileInfoDAT) + else + reset(fileInfoDAT); + + assign(fileNameDAT, dataPath+'fileName.dat'); + if (fsearch(dataPath+'fileName.dat', '') = '') then + rewrite(fileNameDAT) + else + reset(fileNameDAT); + + curFileCount := 0; + foundFileCount := 0; + writeln(' Indexing'); +end; // TIndexer.init + +procedure TIndexer.deleteWordList; +var + wordBTI, wordListBTI:PBTree; + BTSearch:BTreeSearchRec; + u:uPtr; +begin + // To delete the old word list, we need to + // walk through each entry in the wordListBTI and delete + // the fileNameRecordNum out of the word BTI + new(wordListBTI, open(dataPath + hex(fileInfo.wordList.u) + '.bti', NIL)); + while (wordListBTI^.GetFirstKey(BTSearch)) do + begin + new(wordBTI, open(dataPath + hex(int32(BTSearch.key^)) + '.bti', NIL)); + wordBTI^.Delete(@fileInfo.fileNameRecordNum.u, u); + dispose(wordBTI, done); + wordListBTI^.Delete(BTSearch.key, u); + wordListBTI^.findClose(BTSearch); + end; + dispose(wordListBTI, done); +end; // TIndexer.deleteWordList + +function isBinary(const fileName:string):boolean; +type + int8 = shortInt; + int16 = smallInt; + int32 = longInt; + + uInt8 = byte; + uInt16 = word; + uInt32 = dWord; + +type + uInt8ArrayPtr = ^uInt8Array; + uInt8Array = array[ 0..0 ] of uInt8; + +var + f:file; + buf:uInt8ArrayPtr; + i, bytesRead:integer; + invalidCount:integer; +begin + // assumes the file exists + assign(f, fileName); + {$I-} reset(f, 1); {$I+} + if (IOResult() <> 0) then exit; + + getMem(buf, 128); + if (buf = NIL) then + begin + writeln('Out of memory'); + halt + end; + + blockread(f, buf^, 128, bytesRead); + close(f); + invalidCount := 0; + for i:=0 to bytesRead-1 do + if buf^[i] in [0..9, 14..31, 166..255] then inc(invalidCount); + freemem(buf, 128); + result := invalidCount > 32; +end; // isBinary + +function TIndexer.fileType; +type + TExtensionTypes = record + name : string[5]; + ftype : TFileTypes; + end; // TExtensionTypes + +const + nameTypes : array[0..11] of TExtensionTypes = + ((name:'.TXT'; ftype:ftTXT), + (name:'.HTM'; ftype:ftHTML), + (name:'.HTML'; ftype:ftHTML), + (name:'.PAS'; ftype:ftPAS), + (name:'.ASM'; ftype:ftASM), + (name:'.INC'; ftype:ftINC), + (name:'.C'; ftype:ftC), + (name:'.H'; ftype:ftH), + (name:'.I'; ftype:ftI), + (name:'.CPP'; ftype:ftCPP), + (name:'.CC'; ftype:ftCPP), + (name:'.LOG'; ftype:ftLOG) + //(name:'.FW4'; ftype:ftFW4), + //(name:'.BTI'; ftype:ftBTI) + ); // nameTypes + +var + exten : string; + ftype : TFileTypes; + count : integer; + ld : integer; +begin + result := ftUNKNOWN; + + if (filename = '') then exit; + // first try to determine the filetype based on extension + ld := lastDelimiter('.', filename); + if (ld = 0) then exit; + exten := uppercase(copy(filename, ld, length(filename) - ld +1 )); + + ftype := ftUNKNOWN; + for count := low(nameTypes) to high(nameTypes) do + if (nameTypes[count].name = exten) then + begin + if isBinary(filename) then exit; + result := nameTypes[count].ftype; + exit; + end; + // scan the file here to try to figure out what it is +end; // TIndexer.fileType + +function TIndexer.genUniqueFileName; +var + num:uInt32; + tries:uInt32; + newFile:boolean; +begin + tries := 0; + newFile := FALSE; + repeat + num := random(maxlongint); + newFile := fSearch(dataPath+hex(num)+'.bti', '') = ''; + inc(tries); + until (newFile) or (tries > 1000); + if (tries > 1000) then writeln('Should probably put a new checker here'); + result := num; +end; // TIndexer.getUniqueFilename + +function TIndexer.getTimeStamp; +var + dirInfo:SearchRec; + time:longint; +begin + result := 0; + findFirst(filename, anyFile, dirInfo); + if (dosError = 0) then + result := dirInfo.time; + findClose(dirInfo); +end; // TIndexer.getTimeStamp + +procedure TIndexer.parseFile; +var + s:string; + f:file; + p:pCharArrayPtr; + pSize:dword; + curPos:integer; + startPos, endPos:integer; + u:uPtr; + tmpUPtr:uPtr; + wordBTI:PBTree; + wordListBTI:PBTree; + memWordListBTI:PBTree; + code:integer; + value:int64; + bResult:longint; + skipWord:boolean; + ch:char; + tagState:tagType; + +begin + if (fsearch(filename, '') = '') then exit; + assign(f, fileName); + {$I-} reset(f, 1); {$I+} + if (IOResult() <> 0) then exit; + pSize := fileSize(f); + if (pSize = 0) then + begin + close(f); + exit + end; + GetMem(p, pSize); + if (p = NIL) then exit; + blockread(f, p^, pSize, bResult); + if (bResult <> pSize) then writeln('Error reading in from file in TIndexer.parseFile'); + {$I-} close(f); {$I+} + if (IOResult() <> 0) then writeln('Error closing file in TIndexer.parseFile'); + + // update the spinner + gotoxy(length(' Indexing '), 1); + write(spinner[foundFileCount and 3]); + inc(foundFileCount); + + new(wordListBTI, init(dataPath+hex(fileInfo.wordList.u)+'.bti', 256, BT_INT32, NIL)); + new(memWordListBTI, init('', 256, BT_INT32, NIL)); // create a temp word list + + if (p^[0] = '<') then tagState := inTag else tagState := noTag; + + curPos := 0; + case ftype of + ftHTML: + repeat + if tagState = inTag then + begin + if p^[curPos] = '>' then tagState := noTag; + p^[curPos] := ' '; + end + else + begin + if p^[curPos] = '<' then tagState := inTag; + ch := locase(p^[curPos]); + if (ch in ['a'..'z', '0'..'9', '''', '@', '-', '_', #128..#165]) then + p^[curPos] := ch + else + p^[curPos] := ' '; + end; + inc(curPos); + until (curPos >= pSize); + ftTXT, ftPAS..ftLOG: + repeat + ch := locase(p^[curPos]); + if (ch in ['a'..'z', '0'..'9', '''', '_', #128..#165]) then + p^[curPos] := ch + else + p^[curPos] := ' '; + + inc(curPos); + until (curPos >= pSize); + end; // case + + curPos := 0; + startPos := 0; + endPos := 0; + + if p^[0] = ' ' then tagState := noTag else tagState := inTag; + repeat + case tagState of + inTag: + begin + if p^[curPos] = ' ' then + begin + //inTag := FALSE; + tagState := noTag; + endPos := curPos; + // strip off beginning and ending single quotes + if (p^[startPos] = '''') then inc(startPos); + if (p^[endPos-1] = '''') then dec(endPos); + + if (endPos - startPos > 2) and (endPos - startPos <= 64) then + begin + length(s) := endPos - startPos; + move(p^[startPos], s[1], endPos-startPos); + + if (s[1] in ['0'..'9']) and (s[length(s)] in ['0'..'9']) then + begin + val(s, value, code); + skipWord := (code = 0); + end + else skipWord := FALSE; + + if not skipWord then + begin + if (not masterBTI^.Find(@s, u)) then + begin + u.u := genUniqueFileName; + masterBTI^.insert(@s, u); + end; + + // check to see if it's in the cached list + if (not memWordListBTI^.Find(@u, tmpUPtr)) then + begin + new(wordBTI, init(dataPath+hex(u.u)+'.bti', 256, BT_INT32, NIL)); + wordBTI^.insert(@fileInfo.fileNameRecordNum, u); // note that I haven't decided what goes in the value field + dispose(wordBTI, done); + wordListBTI^.Insert(@u, u); // insert into the word list for this file + memWordListBTI^.Insert(@u, u); // insert into the mem word list for this file + end; + end; // !skipWord + end; + end; + end; // inTag + noTag: + begin + if p^[curPos] <> ' ' then + begin + startPos := curPos; + tagState := inTag; + end; + end; // noTag + else + begin + writeln('tagState isn''t defined properly'); + end; + end; {case} + inc(curPos); + until (curPos >= pSize); + dispose(memWordListBTI, done); + dispose(wordListBTI, done); + FreeMem(p, pSize); +end; // TIndexer.parseFile + +procedure TIndexer.Index; +var + filename:string; + fileInfo:TFileInfo; + fileInfoRecordNum:uPtr; + dirInfo:SearchRec; + curDir:string; + skipFile:boolean; + ftype:TFileTypes; + +begin + getDir(0, curDir); + {$I-} chdir(path); {$I+} + if (IOResult() <> 0) then exit; + + path := appendPathDelimiter(path); + findfirst(filemask, Archive + ReadOnly, dirInfo); + while dosError = 0 do + begin + if ((dirInfo.attr and directory) = 0) then + begin + gotoxy(1, 1); + write(spinner[curFileCount and 3]); + inc(curFileCount); + gotoxy(1,24); + clreol; + writeln(path+dirInfo.name); + // clreol; + // writeln('Looking in ', path + dirInfo.name); + + // fillchar(filename, sizeof(filename), 0); + // expand out the filename to the full path + filename := lowercase(fexpand(dirInfo.name)); + ftype := fileType(filename); + if (ftype <> ftUNKNOWN) then + begin + fileInfoRecordNum.offset := 0; + + // clear the fileInfo record + fillchar(fileInfo, sizeof(fileInfo), 0); + + // if (not fileNameBTI^.find(@filename, fileNameRecordNum)) then + if (not fileInfoBTI^.Find(@filename, fileInfoRecordNum)) then + begin + // We haven't come across this file yet + + // fileNameRecordNum represents the offset into the fileNameDAT file + // This offset is used as the key field in the file referenced by + // the value field in master.bti + fileInfo.fileNameRecordNum.offset := filesize(fileNameDAT); + + // wordList is the (numerical) filename of the file that + // holds a list of words in this file. + fileInfo.wordList.u := genUniqueFileName(); + + // timeStamp holds the time of the file. This is used to + // determine whether we have already indexed this file. + // It has a resolution of 2 seconds. + fileInfo.timeStamp.u := getTimeStamp(filename); + + // reserved for future usage. + fileInfo.reserved.offset := 0; // cleared above as well + + // write out this filename to the fileNameDAT file + seek(fileNameDAT, filesize(fileNameDAT)); + blockwrite(fileNameDAT, filename, length(filename)+1); + + fileInfoRecordNum.offset := filesize(fileInfoDAT); + // insert the fileInfoRecordNum (offset into the fileInfoDAT file) + // into the fileInfoBTI + fileInfoBTI^.Insert(@filename, fileInfoRecordNum); + + // write out this file's info to the fileInfoDAT file + seek(fileInfoDAT, fileInfoRecordNum.u); + blockwrite(fileInfoDAT, fileInfo, sizeof(fileInfo)); + skipFile := FALSE; // don't skip this file + end + else + begin + // The file already has been visited. + seek(fileInfoDAT, fileInfoRecordNum.u); + blockread(fileInfoDAT, fileInfo, sizeof(fileInfo)); + skipFile := getTimeStamp(filename) = fileInfo.timeStamp.u; + if not skipFile then + begin + // delete the old word list + deleteWordList(fileInfo); + // update the timestamp field in the fileInfo record + fileInfo.timeStamp.u := getTimeStamp(filename); + seek(fileInfoDAT, fileInfoRecordNum.u); + blockwrite(fileInfoDAT, fileInfo, sizeof(fileInfo)); + end // !skipFile + end; + if not skipFile then parseFile(filename, fileInfo, ftype); + end // if ftype <> ftUNKNOWN + end; + +// if (dirInfo.name[1] <> '.') then Index(path + dirInfo.name, filemask); + + findNext(dirInfo); + end; + + // now recurse directories + findFirst('*.*', Directory, dirInfo); + while (dosError = 0) do + begin + if (dirInfo.name <> '.') and (dirInfo.name <> '..') then + if (lowercase(dirInfo.name) <> 'indicies') then + Index(path + dirInfo.name, filemask); + findNext(dirInfo); + end; // while + + {$I-} chDir(curDir); {$I+} + if (IOResult() <> 0) then writeln('Could not restore previous directory. How odd...'); +end; // TIndexer.Index + +destructor TIndexer.done; +begin + if (masterBTI <> NIL) then dispose(masterBTI, done); + if (fileInfoBTI <> NIL) then dispose(fileInfoBTI, done); + + {$I-} chdir(startingPath); {$I+} + if (IOResult() <> 0) then writeln('Problem restoring dir'); + writeln; + writeln('Done indexing.'); + writeln('Looked in ', curFileCount, ' files'); + writeln('Indexed ', foundFileCount, ' files'); +end; // TIndexer.done + +procedure usage; +begin + writeln('Usage:'); + writeln('bIndex path'); + writeln('Examples:'); + writeln('bIndex dir\'); + writeln('bIndex dir\filename'); + writeln('bIndex dir\files*.*'); + writeln('bIndex c:\*.html'); + halt; +end; // TIndexer.usage + +function getPath:string; +var + Direc : DirStr; + Fname : NameStr; + Exten : ExtStr; +begin + fsplit(paramstr(1), direc, fname, exten); + if (direc = '') then + getDir(0, direc); + result := appendPathDelimiter(direc); +end; // getPath + +function getFileMask:string; +var + Direc : DirStr; + Fname : NameStr; + Exten : ExtStr; +begin + fsplit(paramstr(1), direc, fname, exten); + if (fname = '') then + begin + fname := '*'; + exten := '.*' + end; + result := fname + exten; +end; // getFileMask + +var indexer:PIndexer; + +begin + if (paramcount <> 1) then usage; + randomize; + + new(indexer, init); + indexer^.Index(getPath(), getFileMask()); + dispose(indexer, done); + +end. diff --git a/bInfo.pas b/bInfo.pas new file mode 100755 index 0000000..dbeed4a --- /dev/null +++ b/bInfo.pas @@ -0,0 +1,33 @@ +program bInfo; +uses lists, dos, crt, debug; + +var + tree:PBTree; + inputFileName:string; + outputFileName:string; + ch:char; + +procedure usage; +begin + writeln('bInfo filename.bti output'); + halt; +end; // usage + +begin + if (paramcount <> 2) then usage(); + inputFileName := paramstr(1); + outputFileName := paramstr(2); + if (fsearch(inputFileName, '') = '') then usage(); + if (fsearch(outputFileName, '') <> '') then + begin + writeln(outputFileName, ' already exists. Overwrite? [N/y]'); + repeat + ch := upcase(readkey); + until ch in [#27, #10, 'Y', 'N']; + if (ch <> 'Y') then halt; + end; + new(tree, open(inputFileName, NIL)); + tree^.printWholeTree(outputFileName); + writeln('Tree has ', tree^.getKeyCount(), ' keys'); + dispose(tree, done); +end. \ No newline at end of file diff --git a/bSearch.pas b/bSearch.pas new file mode 100755 index 0000000..77f6bad --- /dev/null +++ b/bSearch.pas @@ -0,0 +1,188 @@ +program bSearch; +uses lists, dos, strings, debug, math, zentimer; + +function lu06(val: Longint): String; +var + i: Longint; + s: String; +begin + Str(val:6,s); + for i := 1 to 6 do + if s[i] = ' ' then s[i]:= '0'; + lu06 := s; +end; + +procedure ReportTime(count: Longint); +var + secs: Longint; +begin + secs := count div 1000000; + count := count - secs * 1000000; + Writeln(secs, '.', lu06(count), ' seconds'); +end; + +type + PBTreeArrayPtr = ^PBTreeArray; + PBTreeArray = array[0..0] of PBTree; + +procedure sortTrees(var trees:PBTreeArrayPtr; treeCount:integer); +var + i, j:integer; + tmpTree:PBTree; +begin + if (trees = NIL) then exit; + for i := 1 to treeCount-1 do + for j := 0 to i do + if (trees^[i]^.getKeyCount < trees^[j]^.getKeyCount) then + begin + tmpTree := trees^[j]; + trees^[j] := trees^[i]; + trees^[i] := tmpTree; + end; +end; // sortTrees + +procedure usage; +begin + writeln('bSearch usage:'); + writeln; + writeln('bSearch term1 [term2] [term3] [...]'); + halt; +end; + +procedure writeHTMLHeader(var f:text); +var curTerm:integer; +begin + writeln(f, ''); + write(f, 'Searching for '); + for curTerm := 1 to paramcount do + write(f, '``', paramstr(curTerm), ''''' '); + writeln(f); + writeln(f, ''); + writeln(f, ''); +end; // writeHTMLHeader + +function convertFileName(const filename:string):string; +var + s:string; + i:integer; +begin + s := 'file:///' + filename; + for i:=1 to length(s) do + if s[i] = '\' then s[i] := '/'; + result := s; +end; // convertFileName + +var + masterBTI:PbTree; + fileNameDAT:file; + treeInfo: BTreeSearchRec; + filename:string; + numFound:integer; + curTerm : integer; + termsFound:integer; + wordBTI:PBTreeArrayPtr; + term:string; + u:uPtr; + outputTXTFile:text; + outputHTMLFile:text; + found:boolean; + +begin + if (paramcount = 0) then usage; + LZTimerOn; + + new(masterBTI, init('indicies\master.bti', 1024, BT_STRING, NIL)); + writeln('masterBTI has ', masterBTI^.getKeyCount(), ' keys'); + assign(fileNameDAT, 'indicies\fileName.dat'); + {$I-} reset(fileNameDAT); {$I+} + if (IOResult <> 0) then + begin + writeln('Could not open fileName.dat'); + halt + end; + GetMem(wordBTI, paramcount * sizeof(PBTree)); + for curTerm := 1 to paramcount do + wordBTI^[curTerm] := NIL; + + assign(outputTXTFile, 'find.txt'); + rewrite(outputTXTFile); + assign(outputHTMLFile, 'find.html'); + rewrite(outputHTMLFile); + + termsFound := 0; + for curTerm := 1 to paramcount do + begin + numFound := 0; + term := lowercase(paramstr(curTerm)); + if (masterBTI^.Find(@term, u)) then + begin + new(wordBTI^[termsFound], open('indicies\'+hex(u.u)+'.bti', NIL)); + inc(termsFound); + end; + end; // for curTerm + + if (termsFound = paramcount) then + begin + // write out the HTML header + writeHTMLHeader(outputHTMLFile); + + // sort the trees into ascending order by keyCount + sortTrees(wordBTI, termsFound); + + // Now that the trees are ordered, scan through the first tree looking + // for matches in all other trees + if (wordBTI^[0]^.getFirstKey(treeInfo)) then + repeat + found := TRUE; + curTerm := 1; + while (found) and (curTerm < paramCount) do + begin + found := wordBTI^[curTerm]^.Find(treeInfo.key, u); + inc(curTerm) + end; // while + + if (found) then + begin + seek(fileNameDAT, int32(treeInfo.key^)); + blockread(fileNameDAT, filename, 1); + blockread(fileNameDAT, filename[1], length(filename)); + if (fsearch(filename, '') <> '') then + begin + writeln(outputTXTFile, filename); + writeln(outputHTMLFile, '', filename, '
'); + end; + inc(numFound); + end; // if found + until not wordBTI^[0]^.findNext(treeInfo); + + write('Found '); + for curTerm := 1 to paramcount do + write('``', paramstr(curTerm), ''''' '); + write('in ', numFound); + if (numFound = 1) then writeln(' file') else writeln(' files'); + + writeln(outputHTMLFile, ''); + end + else + begin + writeHTMLHeader(outputHTMLFile); + writeln(outputHTMLFile, 'No files found contained your term(s)'); + writeln(outputHTMLFile, ''); + writeln('Did not find any files containing the terms:'); + for curTerm := 1 to paramcount do + write('``', paramstr(curTerm), ''''' '); + writeln; + end; + // delete the trees (note that termsFound might be 0, which means it won't execute the for loop) + for curTerm := 0 to termsFound-1 do + if (wordBTI^[curTerm]) <> NIL then dispose(wordBTI^[curTerm], done); + FreeMem(wordBTI, paramcount * sizeof(PBTree)); + close(outputHTMLFile); + close(outputTXTFile); + dispose(masterBTI, done); + close(fileNameDAT); + + LZTimerOff; + write('Search took '); + reportTime(LZTimerCount); +end. \ No newline at end of file diff --git a/backupbtree.pas b/backupbtree.pas new file mode 100755 index 0000000..5d9e605 --- /dev/null +++ b/backupbtree.pas @@ -0,0 +1,821 @@ +unit BPTree; + +interface + +uses UbixFS, strings; + +const B_NODE_MAGIC = $DEADBEEF; + +const B_MAX_KEYS = 15; +const B_MAX_NAME_LENGTH = 256; + +type + PbNode = ^TbNode; + PbNodeData = ^TbNodeData; + + uPtr = packed record + case integer of + 0: (iAddr : inodeAddr); + 1: (bPtr : PbNode); + 2: (vPtr : pointer); + 3: (iPtr : PubixfsInode); + 4: (nPtr : PbNodeData); + 5: (offset : int64); + end; + + TbNode = packed record + magic1 : uInt32; + numKeys : uInt32; + tag : uInt32; + size : uInt32; + leaf : longbool; + reserved : uInt32; + left : uPtr; + right : uPtr; + parent : uPtr; + data : array[0..4040-1] of byte; + end; + + TbNodeData = packed record + link : uPtr; + key : string[1]; + end; {TbNode} + + (* + TbNode = packed record + magic1 : uInt32; + used : uInt32; + parent : uPtr; + tag : int64; + keys : array[0..B_MAX_KEYS-1, 0..B_MAX_NAME_LENGTH-1] of char; + present: array[0..B_MAX_KEYS] of boolean; + head : array[0..B_MAX_KEYS] of uPtr; + childCount : array[0..B_MAX_KEYS] of uInt32; + magic2 : uInt32; + leaf : boolean; + reserved : array[0..18] of byte; + end; {bNode} + *) +type + PbTreeHeader = ^TbTreeHeader; + TbTreeHeader = packed record + treeDepth : uInt32; + treeWidth : uInt32; + treeLeafCount : uInt32; + firstNodeOffset : int64; + firstDeleted : int64; + paddington : array[0..4067] of byte; + end; {bTreeHeader} + +type + PbTree = ^bTree; + bTree = object + private + root : PbNode; + curTag : uInt32; +{ fs : PUbixFS; + header : PbTreeHeader; + fd : PfileDescriptor; + tag : uInt32;} + function allocEmptyNode:PbNode; +{ function getFirstNode(node:PbNode):PubixfsInode; + function inodeSearch(inode:PubixfsInode; const key:PChar):PubixfsInode; + procedure insertNode(node:PbNode; const key:PChar; headPtr:PbNode); + procedure splitNode(oldNode:PbNode); + function treeSearch(bnode:PbNode; const key:PChar):PubixfsInode;} + public + constructor init; + function Insert(key:PChar; var value):boolean; +{ function Find(key:PChar):PubixfsInode;} + procedure Info(node:PbNode); +{ function Insert(inode:PubixfsInode):boolean; + procedure Print(node:PbNode); + procedure PrintWholeTree;} + destructor done; + end; {bTree} + +implementation + +uses strings, dos, crt; + +constructor bTree.init; +begin + root := NIL; + curTag := 0; +end; {bTree.init} + +function bTree.allocEmptyNode; +var newNode:PbNode; +begin + new(newNode); + fillchar(newNode^, sizeof(newNode^), 0); + inc(curTag); + with newNode^ do + begin + magic1 := B_NODE_MAGIC; + numKeys := 1; + tag := curTag; + leaf := FALSE; { assume? } + left.vPtr := NIL; + right.vPtr := NIL; + parent.vPtr := NIL; + + size := sizeof(magic1) + sizeof(numKeys) + sizeof(tag) + + sizeof(leaf) + sizeof(left) + sizeof(right) + + sizeof(reserved) + sizeof(parent) + sizeof(size) + + sizeof(uPtr); +{ writeln('allocated size is: ', size);} + end; {with} + result := newNode; +end; {bTree.allocEmptyNode} + +procedure bTree.Info; +var + nodeData:PbNodeData; + i:integer; + +begin + if ((node = NIL) or (root = NIL)) then exit; + + writeln(hex(longint(node)), ' | ', hex(longint(node^.parent.bPtr))); + + nodeData := @node^.data; + for i := 0 to node^.numKeys do + begin + write('['); + if (nodeData^.link.iPtr <> NIL) then + write(nodeData^.link.iPtr^.name) + else + write('/'); + write(']'); + if (i <> node^.numKeys) then + write(' {', nodeData^.key, '} '); + inc(uInt8ArrayPtr(nodeData), ((sizeof(TbNodeData) + length(nodeData^.key) + 3) shr 2) shl 2); + end; {for i} + writeln; +end; {bTree.Info} + +function bTree.Insert; +var + dataNode, nextDataNode:PbNodeData; + nodeSize, tmpSize:uInt32; + keyLength:integer; + bnode:PbNode; + curNode : integer; + p, src, dst:pointer; + f:file; +begin + result := FALSE; + + keyLength := strlen(key); + if (keyLength = 0) or (keyLength > 255) then exit; + + nodeSize := ((sizeof(TbNodeData) + keyLength + 7) shr 3) shl 3; +{ writeln('nodeSize: ', nodeSize); + nodeSize := sizeof(TbNodeData) + keyLength; + inc(nodeSize, 8 - (nodeSize and not -8));} + + if (root = NIL) then + begin + writeln('-- 1 --'); + root := allocEmptyNode; + if (root = NIL) then halt; + + dataNode := @root^.data; + + fillchar(dataNode^, nodeSize, 0); + + dataNode^.link.iPtr := NIL; + dataNode^.key[0] := char(keyLength); + move(key^, dataNode^.key[1], keyLength); + + with root^ do + begin + inc(uInt8ArrayPtr(dataNode), nodeSize); {cast to byte size records} + dataNode^.link.iPtr := uPtr(value).iPtr; + leaf := TRUE; + inc(size, nodeSize); + end; +info(root); + result := TRUE; + exit + end; + + bnode := root; + + while not bnode^.leaf do + begin + end; + + dataNode := @bnode^.data; + + with bNode^ do + if (numKeys = 1) and (dataNode^.link.iPtr = NIL) then + begin + if (strcomp(key, @dataNode^.key[1]) < 0) then + dataNode^.link.iPtr := uPtr(value).iPtr + else + begin + writeln('-- 2 --'); + nodeSize := ((sizeof(TbNodeData) + length(dataNode^.key) + 7) shr 3) shl 3; + // decrease the size by the size of the first node + dec(size, nodeSize); + + nextDataNode := dataNode; + inc(uInt8ArrayPtr(nextDataNode), nodeSize); {cast to byte size records} + dataNode^.link.iPtr := nextDataNode^.link.iPtr; + + // make a string + + dataNode^.key[0] := char(keyLength); + move(key^, dataNode^.key[1], keyLength); + + nodeSize := ((sizeof(TbNodeData) + length(dataNode^.key) + 7) shr 3) shl 3; + inc(uInt8ArrayPtr(dataNode), nodeSize); {cast to byte size records} + dataNode^.link.iPtr := uPtr(value).iPtr; + // readjust the size based on the size of the new data + inc(size, nodeSize); + end; +info(bnode); + result := TRUE; + exit + end; {if} + + + curNode := 0; + repeat + if (strcomp(key, @dataNode^.key[1]) < 0) then break; + inc(uInt8ArrayPtr(dataNode), ((sizeof(TbNodeData) + length(dataNode^.key) + 7) shr 3) shl 3); {cast to byte size records} + inc(curNode); + until curNode = bnode^.numKeys; + + if (curNode = bnode^.numKeys) then + begin + writeln('--- 3 ---'); + nextDataNode := dataNode; + inc(uInt8ArrayPtr(nextDataNode), nodeSize); {cast to byte size records} + + dataNode^.key[0] := char(keyLength); + move(key^, dataNode^.key[1], keyLength); + nextDataNode^.link.iPtr := uPtr(value).iPtr; + inc(bNode^.size, nodeSize); + end + else + begin + if (curNode = 0) and + ((dataNode^.link.iPtr <> NIL) and (strcomp(key, dataNode^.link.iPtr^.name) < 0)) then + begin + writeln('---- 4a ----'); + + // we need to move the data over by the size of the name in the first link + // note that we might need to split the node first + + keyLength := strlen(dataNode^.link.iPtr^.name); + nodeSize := ((sizeof(TbNodeData) + keyLength + 7) shr 3) shl 3; + nextDataNode := dataNode; + inc(uInt8ArrayPtr(nextDataNode), nodeSize); {cast to byte size records} + move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bNode))); + + dataNode^.key[0] := char(keyLength); + move(dataNode^.link.iPtr^.name, dataNode^.key[1], keyLength); + dataNode^.link.iPtr := uPtr(value).iPtr; + nodeSize := ((sizeof(TbNodeData) + keyLength + 7) shr 3) shl 3; + inc(bNode^.size, nodeSize); + end + else + begin + writeln('---- 4b ----'); + nextDataNode := dataNode; + inc(uInt8ArrayPtr(nextDataNode), nodeSize); {cast to byte size records} + move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bNode))); + dataNode^.key[0] := char(keyLength); + move(key^, dataNode^.key[1], keyLength); + nextDataNode^.link.iPtr := uPtr(value).iPtr; + inc(bNode^.size, nodeSize); + end; + end; + + inc(bnode^.numKeys); +info(bnode); + + result := TRUE; +end; {bTree.Insert} + +destructor bTree.done; +var f:file; +begin + if (root <> NIL) then + begin + assign(f, 'tree.dat'); + rewrite(f, sizeof(TbNode)); + blockwrite(f, root^, 1); + close(f); + end; + if (root<>NIL) then dispose(root); +end; {bTree.done} + + +{$ifdef foo} +constructor bTree.init; +begin + root := NIL; + tag := 0; + new(header); + if (header = NIL) then halt; + fillchar(header^, sizeof(TbTreeHeader), 0); + with header^ do + begin + treeDepth := 1; + treeWidth := 0; + treeLeafCount := 0; + firstDeleted := -1; + firstNodeOffset := sizeof(TbTreeHeader); + end; {with} + if (inode = NIL) then exit; + root := allocEmptyNode; + if (root = NIL) then exit; + + with root^ do + begin + used := 1; + parent.bPtr := NIL; + leaf := true; + childCount[1] := 1; + move(inode^.name, keys[0], B_MAX_NAME_LENGTH); + head[1].iPtr := inode; + present[1] := TRUE; + end; {with} + + with inode^ do + begin + next.iPtr := NIL; + prev.iPtr := NIL; + end; + +end; {bTree.init} + +function bTree.allocEmptyNode; +var newNode:PbNode; +begin + new(newNode); + if (newNode = NIL) then halt; + fillchar(newNode^, sizeof(TbNode), 0); + inc(tag); + with newNode^ do + begin + magic1 := B_NODE_MAGIC_1; + magic2 := B_NODE_MAGIC_2; + parent.bPtr := NIL; + tag := self.tag; + end; {with} + result := newNode; +end; {bTree.allocEmptyNode} + +function bTree.getFirstNode; +var + tmpNode:PbNode; + i:integer; +begin + tmpnode := node; + result := NIL; + if (tmpNode = NIL) then exit; + + while not(tmpNode^.leaf) do + begin + for i := 0 to tmpNode^.used-1 do + begin + if (tmpnode^.head[i].bPtr <> NIL) then + begin + tmpNode := tmpNode^.head[i].bPtr; + break; + end; {if} + end; {for i} + end; {while} + + for i := 0 to tmpNode^.used do + begin + if (tmpNode^.head[i].iPtr <> NIL) then + begin + result := tmpNode^.head[i].iPtr; + exit; + end; {if} + end; {for i} +end; {bTree.getFirstNode} + +function bTree.inodeSearch; +var i:integer; +begin + result := NIL; + if ((inode = NIL) or (key = NIL)) then exit; + + i := strcomp(inode^.name, key); + if (i = 0) then + begin + result := inode; + exit + end; + + if (i < 0) then + begin + inode := inode^.next.iPtr; + if (inode <> NIL) then + repeat + i := strcomp(inode^.name, key); + inode := inode^.next.iPtr; + until ((inode = NIL) or (i >= 0)); + end + else + begin + inode := inode^.prev.iPtr; + if (inode <> NIL) then + repeat + i := strcomp(inode^.name, key); + inode := inode^.prev.iPtr; + until ((inode = NIL) or (i <= 0)); + end; + if (i = 0) then result := inode; +end; {bTree.inodeSearch} + +procedure bTree.insertNode; +var + curSlot : integer; + shift : integer; +begin + if ((node = NIL) or (key = NIL)) then exit; + + with node^ do + if (strcomp(key, keys[used-1]) >= 0) then + begin + curSlot := used; + fillchar(keys[curSlot], B_MAX_NAME_LENGTH, 0); + move(key, keys[curSlot], B_MAX_NAME_LENGTH); + head[curSlot+1].bPtr := headPtr; + present[curSlot+1] := TRUE; + childCount[used] := 0; + end + else + begin + for curSlot := 0 to used-1 do + if (strcomp(key, keys[curSlot]) < 0) then break; + + (* + * note that there is one more item for everything but keys + * So, make the shift count +1 and just subtract it from the key shift + * later + *) + + shift := used - curSlot + 1; + + move(head[curSlot], head[curSlot+1], sizeof(head[0]) * shift); + + move(present[curSlot], present[curSlot+1], sizeof(present[0]) * shift); + + move(childCount[curSlot], childCount[curSlot+1], sizeof(childCount[0]) * shift); + + move(keys[curSlot], keys[curSlot+1], sizeof(keys[0]) * (shift-1)); + + fillchar(keys[curSlot], B_MAX_NAME_LENGTH, 0); + move(key, keys[curSlot], B_MAX_NAME_LENGTH); + head[curSlot+1].bPtr := headPtr; + present[curSlot+1] := TRUE; + end; + + inc(node^.used); + if (node^.used = B_MAX_KEYS) then splitNode(node); +end; {bTree.insertNode} + +procedure bTree.splitNode; +var + tmpInode : PubixfsInode; + newNode : PbNode; + shift : integer; + splitLoc : integer; + i : integer; +begin + tmpInode := NIL; + if (oldNode = NIL) then halt; + if (oldNode^.used <> B_MAX_KEYS) then exit; + + newNode := allocEmptyNode; + if (newNode = NIL) then halt; + + shift := B_MAX_KEYS shr 1; + splitLoc := B_MAX_KEYS - shift; + inc(shift); + + newNode^.used := B_MAX_KEYS shr 1; + oldNode^.used := B_MAX_KEYS shr 1; + + newNode^.parent.bPtr := oldNode^.parent.bPtr; + newNode^.leaf := oldNode^.leaf; + + move(oldNode^.keys[splitLoc], + newNode^.keys[0], + sizeof(newNode^.keys[0]) * (shift-1)); + + fillchar(oldNode^.keys[splitLoc], sizeof(newNode^.keys[0]) * (shift-1), 0); + + move(oldNode^.present[splitLoc], + newNode^.present[0], + sizeof(newNode^.present[0]) * shift); + + fillchar(oldNode^.present[splitLoc], sizeof(oldNode^.present[0]) * shift, 0); + + move(oldNode^.head[splitLoc], newNode^.head[0], sizeof(newNode^.head[0]) * shift); + + fillchar(oldNode^.head[splitLoc], sizeof(newNode^.head[0]) * shift, 0); + + move(oldNode^.childCount[splitLoc], + newNode^.childCount[0], + sizeof(newNode^.childCount[0]) * shift); + + fillchar(oldNode^.childCount[splitLoc], sizeof(oldNode^.childCount[0]) * shift, 0); + + if (not newNode^.leaf) then + for i := 0 to newNode^.used do + newNode^.head[i].bPtr^.parent.bPtr := newNode; + + tmpInode := GetFirstNode(newNode); + if (tmpInode = NIL) then halt; + + if (oldNode = root) then + begin + inc(header^.treeDepth); + root := allocEmptyNode; + oldNode^.parent.bPtr := root; + newNode^.parent.bPtr := root; + + with root^ do + begin + move(tmpInode^.name, keys[0], B_MAX_NAME_LENGTH); + + head[0].bPtr := oldNode; + head[1].bPtr := newNode; + used := 1; + leaf := FALSE; + present[0] := TRUE; + present[1] := TRUE; + childCount[0] := 0; + childCount[1] := 0; + end + end + else + begin + insertNode(newNode^.parent.bPtr, tmpInode^.name, newNode); + end; {else} +end; {bTree.splitNode} + +function bTree.treeSearch; +var i:integer; +begin +{ writeln('treeSearch(', hex(longint(bnode)), ', "', key, '");');} + result := NIL; + if ((bnode = NIL) or (key = NIL) or (bnode^.used = 0)) then exit; + + if (bnode^.leaf) then + begin + result := inodeSearch(GetFirstNode(bnode), key); + exit; + end; + + if (strcomp(key, bnode^.keys[0]) < 0) then + begin + result := treeSearch(bnode^.head[0].bPtr, key); + exit; + end; + + if (strcomp(key, bnode^.keys[bnode^.used-1]) >= 0) then + begin + result := treeSearch(bnode^.head[bnode^.used].bPtr, key); + exit; + end; + + for i := 1 to bnode^.used-1 do + begin + if (strcomp(key, bnode^.keys[i]) < 0) then + begin + result := treeSearch(bnode^.head[i].bPtr, key); + exit; + end; {if} + end; {for i} +end; + +function bTree.Find; +begin + result := treeSearch(root, key); +end; {bTree.Find} + +procedure bTree.Info; +var i:integer; +begin + if ((node = NIL) or (root = NIL)) then exit; + + writeln(hex(longint(node)), ' | ', hex(longint(node^.parent.bPtr))); + + for i := 0 to node^.used do + begin + if (node^.head[i].iPtr <> NIL) then + write('[', node^.head[i].iPtr^.name, ']'); + if (i <> node^.used) then + write(' {', node^.keys[i], '} '); + end; {for i} + writeln; +end; {bTree.Info} + +function bTree.Insert; +var + bNode:PbNode; + tmpInode, iptr:PubixfsInode; + curSlot, i, shift:integer; +begin + result := FALSE; + bNode := root; + tmpInode := NIL; + curSlot := 0; + + if (inode = NIL) then exit; + + if (root = NIL) then + begin + if (header = NIL) then + begin + new(header); + if (header = NIL) then halt; + fillchar(header^, sizeof(TbTreeHeader), 0); + with header^ do + begin + treeDepth := 1; + treeWidth := 0; + treeLeafCount := 0; + firstDeleted := -1; + firstNodeOffset := sizeof(TbTreeHeader); + end; {with} + end; {if header = NIL} + + root := allocEmptyNode; + if (root = NIL) then halt; + + with root^ do + begin + used := 1; + parent.bPtr := NIL; + leaf := true; + childCount[1] := 1; + move(inode^.name, keys[0], B_MAX_NAME_LENGTH); + head[1].iPtr := inode; + present[1] := TRUE; + end; {with} + + with inode^ do + begin + next.iPtr := NIL; + prev.iPtr := NIL; + end; {with} + result := TRUE; + exit; + end; {if root = NIL} + + tmpInode := Find(inode^.name); + if (tmpInode <> NIL) then exit; + inc(header^.treeLeafCount); + if (bnode^.used = 0) then halt; +writeln('---Inserting ', inode^.name, ' @ ', hex(longint(inode))); + + while ((bNode <> NIL) and not bnode^.leaf) do + begin + if (strcomp(inode^.name, bnode^.keys[0]) < 0) then + bnode := bnode^.head[0].bPtr + else + begin + if (strcomp(inode^.name, bnode^.keys[bnode^.used-1]) >= 0) then + bnode := bnode^.head[bnode^.used].bPtr + else + for i := 1 to bnode^.used-1 do + if (strcomp(inode^.name, bnode^.keys[i]) < 0) then + begin + bnode := bnode^.head[i].bPtr; + break + end {if} + end {else} + end; {while} + + if not(bnode^.leaf) then writeln('WARNING: NOT IN LEAF NODE'); + + curSlot := 0; + + if (strcomp(inode^.name, bnode^.keys[0]) < 0) then + tmpInode := bnode^.head[0].iPtr + else + begin + curSlot := bnode^.used; + if (strcomp(inode^.name, bnode^.keys[curSlot-1]) >= 0) then + tmpInode := bnode^.head[curSlot].iPtr + else + begin + for curSlot := 1 to bnode^.used-1 do + if (strcomp(inode^.name, bnode^.keys[curSlot]) < 0) then + begin + tmpInode := bnode^.head[curSlot].iPtr; + break; + end; {if} + tmpInode := bnode^.head[curSlot].iPtr; + end; {else} + end; {else} + + if (tmpInode = NIL) then + begin + (* + * This is the first node in this leaf + *) + bnode^.head[curSlot].iPtr := inode; + bnode^.present[curSlot] := TRUE; + + if (curSlot = 0) then + begin + + if (bnode^.head[1].iPtr <> NIL) then + begin + iptr := bnode^.head[1].iPtr; + inode^.prev.iPtr := iptr^.prev.iPtr; + inode^.next.iPtr := iptr; + iptr^.prev.iPtr := inode; + + if (inode^.prev.iPtr <> NIL) then + inode^.prev.iPtr^.next.iPtr := inode; + end + else + begin + inode^.next.iPtr := NIL; + inode^.prev.iPtr := NIL; + end; + end + else + inc(bnode^.used); + + result := TRUE; + exit; + end; + + writeln('---- before split ----'); + Info(bnode); + + with bnode^ do + if (curSlot <> used) then + begin + shift := used - curSlot +1; + + move(head[curSlot], head[curSlot+1], sizeof(head[0]) * shift); + + move(present[curSlot], present[curSlot+1], sizeof(present[0]) * shift); + + move(childCount[curSlot], childCount[curSlot+1], sizeof(childCount[0]) * shift); + + move(keys[curSlot], keys[curSlot+1], sizeof(keys[0]) * (shift-1)); + fillchar(keys[curSlot], B_MAX_NAME_LENGTH, 0); + end + else + begin + head[curSlot+1] := head[curSlot]; + childCount[curSlot+1] := childCount[curSlot]; + present[curSlot+1] := present[curSlot]; + end; + + with bnode^ do + if (curSlot = 0) then + begin + move(head[1].iPtr^.name, keys[curSlot], B_MAX_NAME_LENGTH); + head[curSlot].iPtr := inode; + end + else + begin + move(inode^.name, keys[curSlot], B_MAX_NAME_LENGTH); + head[curSlot+1].iPtr := inode; + end; + bnode^.present[curSlot] := TRUE; + inc(bnode^.used); + if (bnode^.used = B_MAX_KEYS) then splitNode(bnode); + writeln('--- after split ---'); + Info(bnode); + result := TRUE; +end; {bTree.Insert} + +procedure bTree.Print; +var i:integer; +begin + if (node = NIL) then exit; + Info(node); + if (not node^.leaf) then + for i := 0 to node^.used do + Print(node^.head[i].bPtr); +end; {bTree.Print} + +procedure bTree.PrintWholeTree; +begin + Print(root); +end; {bTree.PrintWholeTree} + +destructor bTree.done; +begin +end; +{$endif} +end. \ No newline at end of file diff --git a/btest.pas b/btest.pas new file mode 100755 index 0000000..9c4aa7d --- /dev/null +++ b/btest.pas @@ -0,0 +1,156 @@ +program btest; + +uses crt, lists, dos, debug; + +var + tree:PBtree; + tree2:PBtree; + u:uPtr; + dirInfo : SearchRec; + s:string; + treeInfo: BTreeSearchRec; + i:integer; +begin + {new(tree, init('', 512, BT_PCHAR));} + writeln('memAvail: ', memAvail); + new(tree, init('', 512, BT_PCHAR)); + (*findfirst('*.*', anyFile, dirInfo); + while dosError = 0 do + begin + u.offset := dirInfo.size; + tree^.Insert(@dirInfo.name, u); + findNext(dirInfo); + end; *) + + + findfirst('*.*', anyFile, dirInfo); + while dosError = 0 do + begin + u.offset := dirInfo.size; + dirInfo.name[length(dirInfo.name)+1]:=#0; + tree^.Insert(@dirInfo.name[1], u); + findNext(dirInfo); + end; + + + findfirst('e:\program files\tmt.5\htmlhelp\*.htm', anyFile, dirInfo); + while dosError = 0 do + begin + u.offset := dirInfo.size; + dirInfo.name[length(dirInfo.name)+1]:=#0; + tree^.Insert(@dirInfo.name[1], u); + +{ tree^.Insert(@dirInfo.name, u);} + findNext(dirInfo); + end; + tree^.printWholeTree; +{--- + findfirst('c:\mp3\*.*', anyFile, dirInfo); + while dosError = 0 do + begin + if not tree^.Find(@dirInfo.name, u) then + writeln('Not found: ', dirInfo.name); + findNext(dirInfo); + end; + + findfirst('c:\tmt.5\htmlhelp\*.htm', anyFile, dirInfo); + while dosError = 0 do + begin + if not tree^.Find(@dirInfo.name, u) then + writeln('Not found: ', dirInfo.name); + findNext(dirInfo); + end; +---} +(* + findfirst('*.*', anyFile, dirInfo); + while dosError = 0 do + begin + u.offset := dirInfo.size; + dirInfo.name[length(dirInfo.name)+1] := #0; + {tree^.Insert(@dirInfo.name[1], u);} + + tree^.Insert(@dirInfo.name, u); + {tree^.printWholeTree;} + findNext(dirInfo); + end; + +tree^.printWholeTree; *) +(* + if (tree^.getFirstKey(treeInfo)) then + repeat + tree^.delete(treeInfo.key, treeInfo.value); + {tree^.printWholeTree;} + until not tree^.findNext(treeInfo); +*) + + + findfirst('e:\program files\tmt.5\htmlhelp\*.htm', anyFile, dirInfo); + while dosError = 0 do + begin + dirInfo.name[length(dirInfo.name)+1] := #0; + tree^.Delete(@dirInfo.name[1], u); + {tree^.Delete(@dirInfo.name, u);} +{ tree^.printWholeTree; } + findNext(dirInfo); + end; + tree^.printWholeTree; + findfirst('*.*', anyFile, dirInfo); + while dosError = 0 do + begin + dirInfo.name[length(dirInfo.name)+1] := #0; + tree^.Delete(@dirInfo.name[1], u); + +{ tree^.Delete(@dirInfo.name, u);} + findNext(dirInfo); + end; + tree^.printWholeTree; + + +(* + new(tree2, init('', 512, BT_STRING)); + if (tree^.getLastKey(treeInfo)) then + repeat + tree2^.Insert(treeInfo.key, treeInfo.value); + {tree2^.printWholeTree;} + until not tree^.findPrev(treeInfo); +{tree2^.printWholeTree;} +dispose(tree2, done); +*) +(* findfirst('c:\mp3\*.*', anyFile, dirInfo); + while dosError = 0 do + begin + u.offset := dirInfo.size; + dirInfo.name[length(dirInfo.name)+1] := #0; + {tree^.Insert(@dirInfo.name[1], u);} + + tree^.Delete(@dirInfo.name, u); + tree^.printWholeTree; + findNext(dirInfo); + end; *) +(* + s := '(07)Lee-Cabrera_Feat._Alex_Cartana-Shake_It_(Move_A_Little_Closer).mp3'; + tree^.delete(@s, u); + tree^.printWholeTree; + + s := 'Laura Branigan - Self Control 2004 (Mindworkers Remix).mp3'; + tree^.delete(@s, u); + tree^.printWholeTree; + + s := 'Lazard - 4 O''Clock in the Morning (Radio Edit).mp3'; + tree^.delete(@s, u); + tree^.printWholeTree; + + s := 'Master Blaster - One Night In Bangkok.mp3'; + tree^.delete(@s, u); + + tree^.printWholeTree; +*) + {if (tree^.findFirst(@s, treeInfo)) then} +(* if (tree^.GetFirstKey(treeInfo)) then + repeat + writeln(string(treeInfo.key^), ' is ', treeInfo.value.offset, ' bytes'); + until not tree^.findNext(treeInfo); + if (treeInfo.key <> NIL) then writeln('key <> nil!'); *) + dispose(tree, done); +writeln('memAvail: ', memAvail); +end. \ No newline at end of file diff --git a/find.html b/find.html new file mode 100755 index 0000000..a5214d1 --- /dev/null +++ b/find.html @@ -0,0 +1,26 @@ + +Searching for ``begin'' + + +h:\ubixfs\backupbtree.pas
+h:\ubixfs\bindex.pas
+h:\ubixfs\binfo.pas
+h:\ubixfs\bptree.pas
+h:\ubixfs\bsearch.pas
+h:\ubixfs\btest.pas
+h:\ubixfs\diskdrv.pas
+h:\ubixfs\fsabstra.pas
+h:\ubixfs\gtime.pas
+h:\ubixfs\lists.pas
+h:\ubixfs\listsbak.pas
+h:\ubixfs\mkufs.pas
+h:\ubixfs\mulit.pas
+h:\ubixfs\mytest.pas
+h:\ubixfs\ramdrive.pas
+h:\ubixfs\testfs.pas
+h:\ubixfs\teststr.pas
+h:\ubixfs\treetest.pas
+h:\ubixfs\ubixfs.pas
+h:\ubixfs\uformat.pas
+h:\ubixfs\testseek.pas
+ diff --git a/graphics.pas b/graphics.pas new file mode 100755 index 0000000..c589c7b --- /dev/null +++ b/graphics.pas @@ -0,0 +1,562 @@ +// Note: This is an example of how *not* to write code + +procedure TUbixFS.visualize; +type + TViewKey = record + message:string; + colour:array[0..3] of ogRGBA8; + end; + blockTypes = (BT_Free, BT_Used, BT_UsedCurrent, BT_InodeDir, BT_InodeFile, BT_BAT, BT_SB); + blockTypeArray = array[0..0] of blockTypes; +const + X_BLOCKS = 32; + Y_BLOCKS = 32; + BLOCKS_PER_PAGE = X_BLOCKS*Y_BLOCKS; + X_SIZE = (768-(X_BLOCKS*2)) div X_BLOCKS; + Y_SIZE = (768-(Y_BLOCKS*2)) div Y_BLOCKS; + viewKey:array[blockTypes] of TViewKey = + ((message:'Free Block'; colour:((red:127; green:127; blue:127; alpha:255), + (red:63; green:63; blue:63; alpha:255), + (red:31; green:31; blue:31; alpha:255), + (red:63; green:63; blue:63; alpha:255))), + (message:'Used Block'; colour:((red:63; green:223; blue:127; alpha:255), + (red:31; green:111; blue:63; alpha:255), + (red:15; green:55; blue:31; alpha:255), + (red:31; green:111; blue:63; alpha:255))), + (message:'Used Blocks (current)'; colour:((red:255; green:255; blue:255; alpha:255), + (red:127; green:127; blue:127; alpha:255), + (red:63; green:63; blue:63; alpha:255), + (red:127; green:127; blue:127; alpha:255))), + (message:'Inode (directory)'; colour:((red:255; green:255; blue:000; alpha:255), + (red:127; green:127; blue:000; alpha:255), + (red:63; green:63; blue:000; alpha:255), + (red:127; green:127; blue:000; alpha:255))), + (message:'Inode (file)'; colour:((red:000; green:255; blue:255; alpha:255), + (red:000; green:127; blue:127; alpha:255), + (red:000; green:063; blue:063; alpha:255), + (red:000; green:127; blue:127; alpha:255))), + (message:'Inode (BAT)'; colour:((red:063; green:127; blue:255; alpha:255), + (red:031; green:63; blue:127; alpha:255), + (red:015; green:31; blue:63; alpha:255), + (red:031; green:63; blue:127; alpha:255))), + (message:'SuperBlock'; colour:((red:240; green:000; blue:063; alpha:255), + (red:120; green:000; blue:031; alpha:255), + (red:55; green:000; blue:015; alpha:255), + (red:120; green:000; blue:031; alpha:255))) + ); // viewKey; +var + font:^ogBitFont; + dataMap:^blockTypeArray; + xBlocks, yBlocks:uInt32; + curIndex:uInt32; + displayY:uInt32; + curX, curY:uInt32; + curPage:uInt32; + xx, yy:uInt32; + u:uPtr; + inode:PUbixFSInode; + b:byte; + ky:char; + map:^ogSurface; + display:^ogSurface; + colours:array[0..3] of ogRGBA8; + points:array[0..3] of ogPoint2D; +label + safeExit; + procedure displayKey; + var index:blockTypes; + begin + if (screen = NIL) or (font = NIL) then exit; + for index := high(viewKey) downto low(viewKey) do + with font^, display^, viewKey[index] do + begin + with points[3] do begin x:=0; y:=ogGetMaxY() - ord(index)*Y_SIZE; end; + with points[2] do begin x:=X_SIZE; y:=ogGetMaxY() - (ord(index))*Y_SIZE; end; + with points[1] do begin x:=X_SIZE; y:=ogGetMaxY() - (ord(index)+1)*Y_SIZE; end; + with points[0] do begin x:=0; y:=ogGetMaxY() - (ord(index)+1)*Y_SIZE; end; + ogFillGouraudPolygon(4, points, colour); +// ogFillRect(0, ogGetMaxY() - ord(index)*Y_SIZE, + // X_SIZE, ogGetMaxY() - (ord(index)+1)*Y_SIZE, + // ogRGB(red, green, blue)); + putString(display^, (X_SIZE*3 div 2), ogGetMaxY() - ord(index)*Y_SIZE - ((getHeight()+Y_SIZE) div 2), message); + end; + end; // local function displayKey + procedure displayMini; + var + yy, xx, index:uInt32; + begin + with screen^ do + begin + index := 0; + ogVLine(X_SIZE*(X_BLOCKS+1)-(X_SIZE div 2), 0, ogGetMaxY(), ogRGB(0, 0, 0)); + ogVLine(X_SIZE*(X_BLOCKS+1)+X_BLOCKS+(X_SIZE div 2), 0, ogGetMaxY(), ogRGB(0, 0, 0)); + + for yy := 0 to ogGetMaxY() do + for xx := 0 to X_BLOCKS-1 do + if (index < uInt32(superBlock^.numBlocks)) then + with viewKey[dataMap^[index]], colour[0] do + begin + ogSetPixel(X_SIZE*(X_BLOCKS+1)+xx, yy, ogRGB(red, green, blue)); + inc(index); + end; + ogVLine(X_SIZE*(X_BLOCKS+1)-(X_SIZE div 2), curPage*Y_BLOCKS, (curPage+1)*Y_BLOCKS-1, ogRGB(255, 255, 255)); + ogVLine(X_SIZE*(X_BLOCKS+1)+X_BLOCKS+(X_SIZE div 2), curPage*Y_BLOCKS, (curPage+1)*Y_BLOCKS-1, ogRGB(255, 255, 255)); + + end; // with + end; // local function displayMini + procedure displayBlock(xx, yy, index:integer; displayCursor:boolean); + var oldBlending:boolean; + begin + if (screen = NIL) then exit; + with map^ do + if (index < uInt32(superBlock^.numBlocks)) then + with viewKey[dataMap^[index]] do + begin + with points[0] do begin x := xx*X_SIZE; y := yy*Y_SIZE; end; + with points[1] do begin x := (xx+1)*X_SIZE-1; y := yy*Y_SIZE; end; + with points[2] do begin x := (xx+1)*X_SIZE-1; y := (yy+1)*Y_SIZE-1; end; + with points[3] do begin x := xx*X_SIZE; y := (yy+1)*Y_SIZE-1; end; + ogFillGouraudPolygon(4, points, colour); + if (displayCursor) then + begin + oldBlending := ogSetBlending(TRUE); + with colours[2] do begin red:=255; green:=255; blue:=255; alpha:=255; end; + with colours[1] do begin red:=127; green:=127; blue:=127; alpha:=127; end; + with colours[0] do begin red:=63; green:=63; blue:=63; alpha:=63; end; + + colours[3] := colours[1]; +(* with points[0] do begin x := xx*X_SIZE; y := yy*Y_SIZE; end; + with points[1] do begin x := (xx+1)*X_SIZE; y := yy*Y_SIZE; end; + with points[2] do begin x := (xx+1)*X_SIZE; y := (yy+1)*Y_SIZE; end; + with points[3] do begin x := xx*X_SIZE; y := (yy+1)*Y_SIZE; end; *) + ogFillGouraudBSpline(4, points, 16,colours); + ogSetBlending(oldBlending); + end; + end + else + ogFillRect(xx*X_SIZE, yy*Y_SIZE, (xx+1)*X_SIZE-1, (yy+1)*Y_SIZE-1, ogRGB(0, 0, 0)); + end; + procedure displayMap; + var + xx, yy, zz:uInt32; + index:uInt32; + oldBlending:boolean; + begin + if (screen = NIL) then exit; + index := curPage * Y_BLOCKS * X_BLOCKS; + for yy := 0 to Y_BLOCKS-1 do + for xx := 0 to X_BLOCKS-1 do + begin + displayBlock(xx, yy, index, FALSE); +(* + with map^ do + if (index < uInt32(superBlock^.numBlocks)) then + with viewKey[dataMap^[index]], colour[0] do + begin + with points[0] do begin x := xx*X_SIZE; y := yy*Y_SIZE; end; + with points[1] do begin x := (xx+1)*X_SIZE; y := yy*Y_SIZE; end; + with points[2] do begin x := (xx+1)*X_SIZE; y := (yy+1)*Y_SIZE; end; + with points[3] do begin x := xx*X_SIZE; y := (yy+1)*Y_SIZE; end; + ogFillGouraudPolygon(4, points, colour); +// ogFillRect(xx*X_SIZE, yy*Y_SIZE, (xx+1)*X_SIZE-1, (yy+1)*Y_SIZE-1, ogRGB(red, green, blue)); + if (index = curIndex) then + begin + oldBlending := ogSetBlending(TRUE); +// with colours[2] do begin red:=255; green:=255; blue:=255; alpha:=223; end; +// with colours[1] do begin red:=127; green:=127; blue:=127; alpha:=160; end; +// with colours[0] do begin red:=63; green:=63; blue:=63; alpha:=127; end; + with colours[2] do begin red:=255; green:=255; blue:=255; alpha:=255; end; + with colours[1] do begin red:=127; green:=127; blue:=127; alpha:=127; end; + with colours[0] do begin red:=63; green:=63; blue:=63; alpha:=63; end; + + colours[3] := colours[1]; + with points[0] do begin x := xx*X_SIZE; y := yy*Y_SIZE; end; + with points[1] do begin x := (xx+1)*X_SIZE; y := yy*Y_SIZE; end; + with points[2] do begin x := (xx+1)*X_SIZE; y := (yy+1)*Y_SIZE; end; + with points[3] do begin x := xx*X_SIZE; y := (yy+1)*Y_SIZE; end; + ogFillGouraudBSpline(4, points, 16,colours); + ogSetBlending(oldBlending); + end; + end + else + ogFillRect(xx*X_SIZE, yy*Y_SIZE, (xx+1)*X_SIZE-1, (yy+1)*Y_SIZE-1, ogRGB(0, 0, 0)); *) + inc(index); + end; + displayMini(); + end; // local function displayMap + function iAddrToLogicalBlock(iAddr:inodeAddr):uInt32; with iAddr, superBlock^ do result := (AG shl AGShift) + start; + function logicalBlockToIAddr(logicalBlock:uInt32):inodeAddr; + begin + setIAddr(result, logicalBlock div superBlock^.blocksPerAG, logicalBlock mod superBlock^.blocksPerAG, 1); + end; + + function incDisplayY:uInt32; + begin + result := displayY; + inc(displayY, font^.getHeight()+2); + end; + procedure displayRange; + begin + displayY := 0; + display^.ogFillRect(0, 0, display^.ogGetMaxX(), font^.getHeight()*4+4, display^.ogRGB(0, 0, 0)); + font^.centerTextX(display^, incDisplayY, 'Range'); + font^.centerTextX(display^, incDisplayY, '[0x' + hex(curPage*X_BLOCKS*Y_BLOCKS) + ' - 0x' + hex((curPage+1)*X_BLOCKS*Y_BLOCKS-1) + ']'); + font^.centerTextX(display^, incDisplayY, 'Selected Block'); + font^.centerTextX(display^, incDisplayY, '[0x'+hex(curIndex)+']'); + if (blockCache <> NIL) then font^.putString(screen^, 0, screen^.ogGetMaxY()-Y_SIZE*3 div 2, 'Block cache has ' + intToStr(blockCache^.getKeyCount()) + ' entries'); + end; + procedure displayInodeInfo; + var + inode:PUbixFSInode; + begin + inode := loadBlock(logicalBlockToIAddr(curIndex)); + if (inode = NIL) then exit; + with font^, inode^ do + begin + displayY := getHeight()*6; + with font^, viewKey[dataMap^[curIndex]], colour[0] do + setFGColor(red, green, blue, alpha); + centerTextX(display^, incDisplayY, strPas(name)); + font^.setFGColor(255, 255, 255, 255); + putString(display^, 0, incDisplayY, 'magic........ ' + hex(magic)); + with inodeNum do + putString(display^,0, incDisplayY, 'inodeNum..... ' + intToStr(AG) + ':' + intToStr(start) + ':' + intToStr(len)); + putString(display^, 0, incDisplayY, 'uid.......... ' + intToStr(uid)); + putString(display^, 0, incDisplayY, 'gid.......... ' + intToStr(gid)); + putString(display^, 0, incDisplayY, 'mode......... ' + hex(mode)); + putString(display^, 0, incDisplayY, 'flags........ ' + hex(flags)); + putString(display^, 0, incDisplayY, 'createTime... ' + hex(createTime)); + putString(display^, 0, incDisplayY, 'modifiedTime. ' + hex(modifiedTime)); + with parent do + putString(display^,0, incDisplayY, 'parent....... ' + intToStr(AG) + ':' + intToStr(start) + ':' + intToStr(len)); + with attributes do + putString(display^,0, incDisplayY, 'attributes... ' + intToStr(AG) + ':' + intToStr(start) + ':' + intToStr(len)); + putString(display^, 0, incDisplayY, 'iType........ ' + hex(iType)); + putString(display^, 0, incDisplayY, 'inodeSize.... ' + intToStr(inodeSize)); + with blocks do + begin + putString(display^, 0, incDisplayY, 'size......... ' + intToStr(uInt32(size))); + putString(display^, 0, incDisplayY, 'blockCount... ' + intToStr(blockCount)); + with indirect do + putString(display^, 0, incDisplayY, 'indirect..... ' + intToStr(AG) + ':' + intToStr(start) + ':' + intToStr(len)); + with doubleIndirect do + putString(display^, 0, incDisplayY, 'dIndirect.... ' + intToStr(AG) + ':' + intToStr(start) + ':' + intToStr(len)); + + end; + end; // with + end; + procedure displayDirectoryInodeInfo; + var dirTree:PBTree; + begin + displayInodeInfo(); + new(dirTree, open(IAddrToStr(logicalBlockToIAddr(curIndex)), new(PUbixBTreeVFS, init(@self)))); + font^.putString(display^, 0, incDisplayY, 'tree entries. ' + intToStr(dirTree^.getKeyCount())); + dispose(dirTree, done); + end; + procedure displaySuperBlockInfo; + begin + with font^, superBlock^ do + begin + displayY := getHeight()*6; + with viewKey[BT_SB], colour[0] do + font^.setFGColor(red, green, blue, alpha); + centerTextX(display^, incDisplayY, 'SuperBlock'); + font^.setFGColor(255, 255, 255, 255); + putString(display^, 0, incDisplayY, 'name......... ' + name); + putString(display^, 0, incDisplayY, 'magic1....... ' + hex(magic1)); + putString(display^, 0, incDisplayY, 'fsByteOrder.. ' + intToStr(fsByteOrder)); + putString(display^, 0, incDisplayY, 'blockSize.... ' + intToStr(blockSize)); + putString(display^, 0, incDisplayY, 'blockShift... ' + intToStr(blockShift)); + putString(display^, 0, incDisplayY, 'numBlocks.... ' + intToStr(uInt32(numBlocks))); + putString(display^, 0, incDisplayY, 'usedBlocks... ' + intToStr(uInt32(usedBlocks))); + putString(display^, 0, incDisplayY, 'magic2....... ' + hex(magic2)); + putString(display^, 0, incDisplayY, 'blocksPerAG.. ' + intToStr(blocksPerAG)); + putString(display^, 0, incDisplayY, 'AGShift...... ' + intToStr(AGShift)); + putString(display^, 0, incDisplayY, 'numAGs....... ' + intToStr(numAGs)); + putString(display^, 0, incDisplayY, 'flags........ ' + hex(flags)); + putString(display^, 0, incDisplayY, 'magic3....... ' + hex(magic3)); + with superBlock^.BAT do + putString(display^,0, incDisplayY, 'BAT.......... ' + intToStr(AG) + ':' + intToStr(start) + ':' + intToStr(len)); + with superBlock^.rootDir do + putString(display^,0, incDisplayY, 'rootDir...... ' + intToStr(AG) + ':' + intToStr(start) + ':' + intToStr(len)); + end; // with + end; // displaySuperBlockInfo + procedure markBlocks(iAddr:inodeAddr; value:blockTypes); + var + inode:PUbixfsInode; + index:uInt32; + blockRun:TBlockRun; + begin + inode := loadBlock(iAddr); + if (inode = NIL) then exit; + if (inode^.blocks.blockCount = 0) then exit; + blockRun := inode^.blocks.indirect; + if isValidIAddr(blockRun) then + for index := 0 to blockRun.len-1 do + begin + dataMap^[iAddrToLogicalBlock(blockRun)] := value; + inc(blockRun.start); + end; + blockRun := inode^.blocks.doubleIndirect; + if isValidIAddr(blockRun) then + for index := 0 to blockRun.len-1 do + begin + dataMap^[iAddrToLogicalBlock(blockRun)] := value; + inc(blockRun.start); + end; + for index := 0 to inode^.blocks.blockCount-1 do + dataMap^[iAddrToLogicalBlock(findBRun(@inode^.blocks, index))] := value; + displayMap(); + end; // local function markBlocks + + procedure clearSelection; + begin + displayBlock((curIndex-curPage*BLOCKS_PER_PAGE) mod X_BLOCKS, (curIndex-curPage*BLOCKS_PER_PAGE) div X_BLOCKS, curIndex, FALSE); + if (dataMap^[curIndex] in [BT_InodeDir, BT_InodeFile, BT_BAT]) then + markBlocks(logicalBlockToIAddr(curIndex), BT_Used); + with display^, font^ do + ogFillRect(0, getHeight()*5, ogGetMaxX(), ogGetMaxY() - Y_SIZE*(ord(high(viewKey))+2),display^.ogRGB(0, 0, 0)); + end; + procedure setSelection; + begin + if (dataMap^[curIndex] in [BT_InodeDir, BT_InodeFile, BT_BAT]) then + markBlocks(logicalBlockToIAddr(curIndex), BT_UsedCurrent); + case dataMap^[curIndex] of + BT_SB:displaySuperBlockInfo(); + BT_InodeFile:displayInodeInfo(); + BT_InodeDir, BT_BAT:displayDirectoryInodeInfo(); + end; // case + displayBlock((curIndex-curPage*BLOCKS_PER_PAGE) mod X_BLOCKS, (curIndex-curPage*BLOCKS_PER_PAGE) div X_BLOCKS, curIndex, TRUE); + displayRange(); + + end; + + procedure moveLeft; + var + oldPage:uInt32; + begin + if (curIndex > 0) then + begin + clearSelection(); + dec(curIndex); + oldPage := curPage; + curPage := curIndex div (X_BLOCKS*Y_BLOCKS); + if (oldPage <> curPage) then + begin + displayRange(); + displayMap(); + end; + setSelection(); + end; + end; + procedure moveRight; + var oldPage:uInt32; + begin + if (curIndex < uInt32(superBlock^.numBlocks-1)) then + begin + clearSelection(); + inc(curIndex); + oldPage := curPage; + curPage := curIndex div (X_BLOCKS*Y_BLOCKS); + if (oldPage <> curPage) then + begin + displayRange(); + displayMap(); + end; + setSelection(); + end; + end; + procedure moveUp; + var oldPage:uInt32; + begin + if (curIndex >= X_BLOCKS) then + begin + clearSelection(); + dec(curIndex, X_BLOCKS); + oldPage := curPage; + curPage := curIndex div (X_BLOCKS*Y_BLOCKS); + if (oldPage <> curPage) then + begin + displayRange(); + displayMap(); + end; + setSelection(); + end; + end; + procedure moveDown; + var oldPage:uInt32; + begin + if (curIndex < uInt32(superBlock^.numBlocks-X_BLOCKS)) then + begin + clearSelection(); + inc(curIndex, X_BLOCKS); + oldPage := curPage; + curPage := curIndex div (X_BLOCKS*Y_BLOCKS); + if (oldPage <> curPage) then + begin + displayRange(); + displayMap(); + end; + setSelection(); + end; + end; + procedure pageDown; + begin + if (curPage < (uInt32((superBlock^.numBlocks)+(X_BLOCKS*Y_BLOCKS-1)) div (X_BLOCKS*Y_BLOCKS)-1)) then + begin + inc(curPage); + displayRange(); + displayMap(); + end; + end; + procedure pageUp; + begin + if (curPage > 0) then + begin + dec(curPage); + displayRange(); + displayMap(); + end; + end; + procedure markDirectory(dirAddr:inodeAddr); + var dirTree:PbTree; + inode:PUbixFSInode; + searchRec:BTreeSearchRec; + begin + dataMap^[iAddrToLogicalBlock(dirAddr)] := BT_InodeDir; + new(dirTree, open(IAddrToStr(dirAddr), new(PUbixBTreeVFS, init(@self)))); + dirTree^.getFirstKey(searchRec); + repeat + inode := loadBlock(searchRec.value.iAddr); + if (inode = NIL) then exit; + if (int8ArrayPtr(searchRec.key)^[0] <> ord('.')) then + begin + if (inode^.mode and S_IFDIR <> 0) then + markDirectory(searchRec.value.iAddr) + else + dataMap^[iAddrToLogicalBlock(searchRec.value.iAddr)] := BT_InodeFile; + end; + until not dirTree^.findNext(searchRec); + dispose(dirTree, done); + end; + procedure markAll; +//var f:text; + begin + fillchar(dataMap^, uInt32(superBlock^.numBlocks), BT_Free); +(* inode := loadBlock(superBlock^.BAT); + for yy := 0 to uInt32(inode^.blocks.size)-1 do + if (yy >= uInt32(superBlock^.numBlocks div 8)) then + break + else + begin + readDataStream(superBlock^.BAT, yy, @b, 1); + for xx := 0 to 7 do + if (b and (1 shl (7-xx)) <> 0) then dataMap^[yy*8+xx] := BT_USED; + end; *) + dataMap^[0] := BT_SB; // mark the superblock + dataMap^[iAddrToLogicalBlock(superBlock^.BAT)] := BT_BAT; // mark the BAT + markDirectory(superBlock^.rootDir); +(* system.assign(f, 'bc2.txt'); +system.rewrite(f); +for xx := 0 to uInt32(superBlock^.numblocks)-1 do + if dataMap^[xx] <> BT_Free then writeln(f, hex(xx)); +system.close(f); *) + end; + +var + savebuf:^ogSurface; + imageSaver:^ogImage; + dirTree:PbTree; +begin + if (dev = NIL) or (superBlock = NIL) then exit; + map := NIL; + display := NIL; + dataMap := NIL; + font := NIL; + if (screen = NIL) then new(screen, ogInit); + new(font, init); + if (font = NIL) then exit; + if not (font^.load('ROM8X16.DPF')) then + begin + writeln('Error loading font'); + goto safeExit; + end; + if not (screen^.ogCreate(1024, 768, OG_PIXFMT_32BPP)) then + begin + writeln('Error setting graphics mode'); + goto safeExit; + end; + screen^.ogClear(screen^.ogRGB(0, 0, 0)); + new(map, ogInit); + new(display, ogInit); + if not (display^.ogAlias(screen^, 780, 0, screen^.ogGetMaxX(), screen^.ogGetMaxY())) then goto safeExit; + if not (map^.ogAlias(screen^, 0, 0, X_SIZE*X_BLOCKS-1, Y_SIZE*Y_BLOCKS-1)) then goto safeExit; + + GetMem(dataMap, uInt32(superBlock^.numBlocks)); // Hope that there aren't more than 4 billion blocks + if (dataMap = NIL) then + begin + writeln('Error allocating memory for dataMap'); + goto safeExit; + end; + + markAll(); + + font^.setFGColor(255, 255, 255, 255); + font^.setBGColor(0, 0, 0, 0); + curPage := 0; + curIndex := 0; + curX := 0; + curY := 0; + displayKey(); + displayMap(); + displayRange(); + setSelection(); + repeat + ky := upcase(readkey); + case ky of + #0:case readkey of + #72:moveUp(); + #73:pageUp(); + #75:moveLeft(); + #77:moveRight(); + #80:moveDown(); + #81:pageDown(); + #83:if dataMap^[curIndex] in [BT_InodeFile] then + begin + clearSelection(); + inode := loadBlock(logicalBlockToIAddr(curIndex)); + if (inode = NIL) then continue; + inode^.blocks.size := 0; + shrinkFile(inode); + u.iAddr := inode^.inodeNum; + new(dirTree, open(IAddrToStr(inode^.parent), new(PUbixBTreeVFS, init(@self)))); + dirTree^.Delete(@inode^.name, u); + dispose(dirTree, done); + freeUsedBlocks(inode^.inodeNum); + markAll(); + displayMap(); + setSelection(); + end; + end; // case + '0'..'9':begin + new(imageSaver,init); + new(saveBuf,ogInit); + if (saveBuf^.ogCreate(screen^.ogGetMaxX+1,screen^.ogGetMaxY+1,OG_PIXFMT_24BPP)) then + begin + saveBuf^.ogCopy(screen^); + imageSaver^.saveGfx(saveBuf^,ky+'.bmp',BMP); + end; + dispose(saveBuf,ogDone); + dispose(imageSaver,done); + end; + end; // case + until ky = #27; + +safeExit: + if (dataMap <> NIL) then FreeMem(dataMap, uInt32(superBlock^.numBlocks)); + if (font <> NIL) then dispose(font, done); + if (map <> NIL) then dispose(map, ogDone); + if (display <> NIL) then dispose(display, ogDone); + if (screen <> NIL) then dispose(screen, ogDone); + screen := NIL; +end; // TUbixFS.visualize diff --git a/gtime.pas b/gtime.pas new file mode 100755 index 0000000..40f4a5e --- /dev/null +++ b/gtime.pas @@ -0,0 +1,20 @@ +uses Dos; +var + Fi : Text; + Time: Longint; + DT : DateTime; +begin + Assign(Fi, 'DATA.BAK'); + rewrite(Fi); + GetFTime(Fi, Time); + + UnPackTime(Time, DT); + writeln('Time: ', time); + WriteLn('Year: ', DT.Year); + WriteLn('Month: ', DT.Month); + WriteLn('Date: ', DT.Day); + writeln('Hour: ', DT.hour); + writeln('Minute: ', DT.min); + writeln('Second: ', DT.sec); + Close(Fi); +end. \ No newline at end of file diff --git a/keytest.pas b/keytest.pas new file mode 100755 index 0000000..7c3570a --- /dev/null +++ b/keytest.pas @@ -0,0 +1,8 @@ +uses crt, dos; +var ky:char; +begin + repeat + ky := readkey; + writeln(ord(ky)); + until ky = #27; +end. \ No newline at end of file diff --git a/lists.pas b/lists.pas new file mode 100755 index 0000000..fb90a74 --- /dev/null +++ b/lists.pas @@ -0,0 +1,2352 @@ +{$TPO-} // treat all pointer inc/dec's as byte adjusts +unit lists; + +interface + +type + int8 = shortInt; + int16 = smallInt; + int32 = longInt; + + uInt8 = byte; + uInt16 = word; + uInt32 = dWord; + +type + treeTypes = (BT_CUSTOM, BT_PCHAR, BT_STRING, BT_SINGLE, BT_DOUBLE, BT_INT32, BT_INT64); + PbNode = ^TbNode; + PbNodeData = ^TbNodeData; + + TBlockRun = packed record + AG : int32; + start : uInt16; + len : uInt16; + end; + + inodeAddr = TblockRun; + + uPtr = packed record + case integer of + 0: (i : int32); + 1: (u : uInt32); + 2: (s : single); + 3: (d : double); + 4: (p : pointer); + 5: (bPtr : PbNode); + 6: (offset : int64); + 7: (iAddr : inodeAddr); + end; + + PQueue = ^TQueue; + TQueue = object + private + queue:pointer; + elements:uInt32; + maxElements:uInt32; + size:uInt32; + head, tail:uInt32; + public + constructor init(elementCount, elementSize:uInt32); + function dequeue(var item):boolean; + function enqueue(var item):boolean; + function getElementCount:uInt32; + destructor done; + end; // TQueue + + PBTreeVFS = ^TBTreeVFS; + TBTreeVFS = object + public + constructor init; + function fClose:boolean; virtual; + function fCreate(const filename:string):boolean; virtual; + function fExist(const filename:string):boolean; virtual; + function fOpen(const filename:string):boolean; virtual; + function fRead(var buf; size:uInt32):boolean; virtual; + function fSeek(offset:uInt32):boolean; virtual; + function fWrite(var buf; size:uInt32):boolean; virtual; + destructor done; virtual; + end; // TBTreeVFS + + TbNode = packed record + magic : uInt32; + tag : uInt32; + numKeys : uInt32; + size : uInt32; + left : uPtr; + right : uPtr; + parent : uPtr; + reserved : uPtr; + data : array[0..0] of byte; + end; + + TbNodeData = packed record + link : uPtr; + key : record + case treeTypes of + BT_PCHAR, + BT_STRING: (str:string[1]); + BT_SINGLE: (s:single); + BT_DOUBLE: (d:double); + BT_INT32 : (i:int32); + BT_INT64 : (x:int64); + end; {record} + end; {TbNode} + +type + BTreeSearchRec = record + value : uPtr; + key : pointer; + keySize:integer; + end; {BTreeSearchRec} + +type + PBTreeInfo = ^BTreeInfo; + BTreeInfo = packed record + magic : int64; + root : uPtr; + firstDeleted : uPtr; + nodes : uInt32; + height : uInt32; + keys : uInt32; + bNodeSize : uInt32; + treeType : treeTypes; + end; {BTreeHeader} + +type compareKeyFunc = function(key1, key2:pointer):integer; +type copyKeyProc = procedure(srcKey, destKey:pointer); +type keySizeFunc = function(key:pointer):integer; + +type + PbTree = ^bTree; + bTree = object + private + treeName : string; + compareKeys : compareKeyFunc; + copyKey : copyKeyProc; + keySize : keySizeFunc; + info : PBTreeInfo; + vfs : PBTreeVFS; + //dataFile : file; + memoryTree : boolean; + treeChanged : boolean; + function align(keyLength:integer):integer; + function allocEmptyNode:uPtr; + function calcSize(node:PbNode):integer; + procedure deAllocNode(node:PbNode); + function deleteEntry(u:uPtr; key:pointer; value:uPtr):uPtr; + procedure discardPage(node:PbNode); + function fetchFirstKey(u:uPtr; var searchRec:BTreeSearchRec):boolean; + function fetchLastKey(u:uPtr; var searchRec:BTreeSearchRec):boolean; + function findLeafNode(u:uPtr; key:pointer):uPtr; + procedure freeAll; + procedure freePage(u:uPtr); + function getAddress(node:PbNode):uPtr; + function getRoot:uPtr; + function getFirstDeleted:uPtr; + function getNodes:uInt32; + function getHeight:uInt32; + function getKeys:uInt32; + function insertEntry(u:uPtr; key:pointer; value:uPtr):boolean; + function isNull(u:uPtr):boolean; + function loadPage(u:uPtr):PbNode; + procedure loadSuperBlock; + procedure Print(var f:text; u:uPtr); + procedure PrintInfo(var f:text; u:uPtr); + function replaceEntry(u:uPtr; key:pointer; value:uPtr):boolean; + procedure savePage(node:PbNode); + procedure saveSuperBlock; + procedure setLeft(u:uPtr; newPtr:uPtr); + procedure setNull(var u:uPtr); + procedure setRight(u:uPtr; newPtr:uPtr); + procedure setParent(u:uPtr; newPtr:uPtr); + + procedure setFirstDeleted(value:uPtr); + procedure setNodes(value:uInt32); + procedure setHeight(value:uInt32); + procedure setKeys(value:uInt32); + procedure setRoot(value:uPtr); + public + constructor init(const filename:string; nodeSize:uInt32; tt:treeTypes; virtualFS:PBTreeVFS); + constructor open(const filename:string; virtualFS:PBTreeVFS); + function Delete(key:pointer; value:uPtr):uPtr; + function Insert(key:pointer; value:uPtr):boolean; + procedure InstallUserFunctions(cmpFunc:compareKeyFunc; copyProc:copyKeyProc; ksFunc:keySizeFunc); + function Find(key:pointer; var u:uPtr):boolean; + procedure FindClose(var searchRec:BTreeSearchRec); + function FindFirst(key:pointer; var searchRec:BTreeSearchRec):boolean; + function FindNext(var searchRec:BTreeSearchRec):boolean; + function FindPrev(var searchRec:BTreeSearchRec):boolean; + function GetFirstKey(var searchRec:BTreeSearchRec):boolean; + function GetKeyCount:uInt32; + function GetLastKey(var searchRec:BTreeSearchRec):boolean; + procedure PrintList(const filename:string); + procedure PrintWholeTree(const filename:string); + destructor done; + end; // bTree + +implementation + +uses strings, dos; + +const B_NODE_LEAF = $4641454C; // LEAF +const B_NODE_INDEX = $58444E49; // INDX +const B_NODE_OPEN = $4E45504F; // OPEN +const B_NODE_TREEINFO = $4F464E4945455254; // TREEINFO + +type + PStandardVFS = ^TStandardVFS; + TStandardVFS = object(TBTreeVFS) + private + dataFile:file; + openFile:boolean; + public + constructor init; + function fClose:boolean; virtual; + function fCreate(const filename:string):boolean; virtual; + function fExist(const filename:string):boolean; virtual; + function fOpen(const filename:string):boolean; virtual; + function fRead(var buf; size:uInt32):boolean; virtual; + function fSeek(offset:uInt32):boolean; virtual; + function fWrite(var buf; size:uInt32):boolean; virtual; + destructor done; virtual; + end; // TStandardVFS + +// TVFS virtual abstract functions + +constructor TBTreeVFS.init; +begin + writeln('Don''t call the parent constructor'); +end; // TBTreeVFS.init + +function TBTreeVFS.fClose; result := FALSE; +function TBTreeVFS.fCreate; result := FALSE; +function TBTreeVFS.fExist; result := FALSE; +function TBTreeVFS.fOpen; result := FALSE; +function TBTreeVFS.fRead; result := FALSE; +function TBTreeVFS.fSeek; result := FALSE; +function TBTreeVFS.fWrite; result := FALSE; + +destructor TBTreeVFS.done; +begin + writeln('Don''t call the parent destructor'); +end; // TBTreeVFS.done + +// TStandardVFS methods + +constructor TStandardVFS.init; +begin + openFile := FALSE; +end; // TStandardVFS.init + +function TStandardVFS.fClose; +begin + if (openFile) then {$I-} close(dataFile); {$I+} + openFile := (IOResult() <> 0); + result := not openFile +end; // TStandardVFS.fClose + +function TStandardVFS.fCreate; +begin + assign(dataFile, filename); + {$I-} rewrite(dataFile, 1); {$I+} + openFile := (IOResult() = 0); + result := openFile +end; // TStandardVFS.fCreate + +function TStandardVFS.fExist; +begin + result := (fSearch(filename, '') <> '') +end; // TStandardVFS.fExist + +function TStandardVFS.fOpen; +begin + assign(dataFile, filename); + {$I-} reset(dataFile, 1); {$I+} + openFile := (IOResult() = 0); + result := openFile +end; // TStandardVFS.fOpen + +function TStandardVFS.fRead; +var lResult:uInt32; +begin + blockread(dataFile, buf, size, lResult); + result := ((IOResult() = 0) and (lResult = size)); +end; // TStandardVFS.fRead + +function TStandardVFS.fSeek; +begin + {$I-} seek(dataFile, offset); {$I+} + result := (IOResult() = 0) +end; // TStandardVFS.fSeek + +function TStandardVFS.fWrite; +var lResult:uInt32; +begin + // writeln('fWrite(dataFile, buf, ', count, ') @ position: ', filePos(dataFile)); + blockwrite(dataFile, buf, size, lResult); + result := ((IOResult() = 0) and (lResult = size)); +end; // TStandardVFS.fWrite + +destructor TStandardVFS.done; +begin + if (openFile) then fClose(); +end; // TStandardVFS.done + +function compareKeyNull(key1, key2:pointer):integer; result := 0; // compareKeyNull + +procedure copyKeyNull(srcKey, destKey:pointer); begin end; // copyKeyNull + +function keySizeNull(key:pointer):integer; result := 0; // keySizeNull + +(* + * CompareKey functions + * + *) + +function compareKeyString(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if string(key1^) < string(key2^) then + result := -1 + else + if string(key1^) > string(key2^) then result := 1; +end; {comapreKeyString} + +function compareKeyPChar(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + result := strcomp(pchar(key1), pchar(key2)); +end; {compareKeyPChar} + +function compareKeyDouble(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (double(key1^) < double(key2^)) then + result := -1 + else + if (double(key1^) > double(key2^)) then result := 1; +end; {compareKeyDouble} + +function compareKeySingle(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (single(key1^) < single(key2^)) then + result := -1 + else + if (single(key1^) > single(key2^)) then result := 1; +end; {compareKeySingle} + +function compareKeyInt32(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (int32(key1^) < int32(key2^)) then + result := -1 + else + if (int32(key1^) > int32(key2^)) then result := 1; +end; {compareKeyInt32} + +function compareKeyInt64(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + + if (int64(key1^) < int64(key2^)) then + result := -1 + else + if (int64(key1^) > int64(key2^)) then result := 1; + +end; // compareKeyInt64 + +(* + * CopyKey functions + * + *) + +procedure copyKeyPChar(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + strcopy(pchar(destKey), pchar(srcKey)); +end; {copyeKeyPChar} + +procedure copyKeyString(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + string(destKey^) := string(srcKey^); +end; {copyKeyString} + +procedure copyKeySingle(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + single(destKey^) := single(srcKey^); +end; {copyKeySingle} + +procedure copyKeyDouble(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + double(destKey^) := double(srcKey^); +end; {copyKeyDouble} + +procedure copyKeyInt32(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + int32(destKey^) := int32(srcKey^); +end; {copyKeyInt32} + +procedure copyKeyInt64(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + int64(destKey^) := int64(srcKey^); +end; {copyKeyInt64} + +(* + * KeySize functions + * + *) + +function keySizePChar(key:pointer):integer; +begin + result := 0; + if (key = NIL) then exit; + result := strlen(pchar(key))+1; +end; {keySizePChar} + +function keySizeString(key:pointer):integer; +begin + result := 0; + if (key = NIL) then exit; + result := length(string(key^))+1; +end; {keySizeString} + +function keySizeSingle(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(single); +end; {keySizeSingle} + +function keySizeDouble(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(double); +end; {keySizeDouble} + +function keySizeInt32(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(int32); +end; {keySizeInt32} + +function keySizeInt64(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(int64); +end; {keySizeInt64} + +(********************************) + +function compareUPtrs(u1, u2:uPtr):boolean; +begin + result := (u1.offset = u2.offset) +end; // compareUPtrs + +overload = = compareUPtrs; + +(* + * + * B^Tree object methods + * + *) +(* + magic : int64; + root : uPtr; + firstDeleted : uPtr; + nodes : uInt32; + height : uInt32; + keys : uInt32; + bNodeSize : uInt32; + treeType : treeTypes; +*) +function bTree.getFirstDeleted; +begin + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.firstDeleted)-dword(info)) then runError; + if not vfs^.fRead(info^.firstDeleted, sizeof(info^.firstDeleted)) then RunError; + end; + result := info^.firstDeleted; +end; + +function bTree.getNodes; +begin + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.nodes)-dword(info)) then RunError; + if not vfs^.fRead(info^.nodes, sizeof(info^.nodes)) then RunError; + end; + result := info^.nodes; +end; + +function bTree.getHeight; +begin + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.height)-dword(info)) then RunError; + if not vfs^.fRead(info^.height, sizeof(info^.height)) then RunError; + end; + result := info^.height; +end; + +function bTree.getKeys; +begin + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.keys)-dword(info)) then RunError; + if not vfs^.fRead(info^.keys, sizeof(info^.keys)) then RunError; + end; + result := info^.keys; +end; + +function bTree.getRoot; +begin + if (info = NIL) then + begin + setNull(result); + exit; + end; + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.root)-dword(info)) then RunError; + if not vfs^.fRead(info^.root, sizeof(info^.root)) then RunError; + end; + result := info^.root; +end; + +procedure bTree.setFirstDeleted; +begin + info^.firstDeleted := value; + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.firstDeleted)-dword(info)) then runError; + if not vfs^.fWrite(info^.firstDeleted, sizeof(info^.firstDeleted)) then RunError; + end; +end; + +procedure bTree.setNodes; +begin + info^.nodes := value; + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.nodes)-dword(info)) then RunError; + if not vfs^.fWrite(info^.nodes, sizeof(info^.nodes)) then RunError; + end; +end; + +procedure bTree.setHeight; +begin + info^.height := value; + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.height)-dword(info)) then RunError; + if not vfs^.fWrite(info^.height, sizeof(info^.height)) then RunError; + end; +end; + +procedure bTree.setKeys; +begin + info^.keys := value; + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.keys)-dword(info)) then RunError; + if not vfs^.fWrite(info^.keys, sizeof(info^.keys)) then RunError; + end; +end; + +procedure bTree.setRoot; +begin + info^.root := value; + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.root)-dword(info)) then RunError; + if not vfs^.fWrite(info^.root, sizeof(info^.root)) then RunError; + end; +end; +(* +procedure bTree.setRoot; +begin + info^.keys := value; + if not(memoryTree) then + begin + if not vfs^.fSeek(dword(@info^.keys)-dword(info)) then RunError; + if not vfs^.fWrite(info^.keys, sizeof(info^.keys)) then RunError; + end; +end; +*) + +constructor bTree.init; +var lResult:longint; +begin + memoryTree := fileName = ''; + treeName := filename; + // allocate the info section + // getmem(info, sizeof(BTreeInfo)); + new(info); + if (info = NIL) then runError; + fillchar(info^, sizeof(BTreeInfo), 0); + vfs := virtualFS; + with info^ do + begin + magic := B_NODE_TREEINFO; + setNull(root); + setNull(firstDeleted); + nodes := 0; + height := 0; + keys := 0; + bNodeSize := nodeSize; + treeType := tt; + end; {with} + + if not (memoryTree) then + begin + + if (vfs = NIL) then vfs := new(PStandardVFS, init); + if (vfs^.fExist(filename)) then + begin + if not (vfs^.fOpen(filename)) then runError; + if not vfs^.fRead(info^, sizeof(BTreeInfo)) then writeln('Error reading in bTreeInfo'); + end + else + begin + if not vfs^.fCreate(filename) then runError; + if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error writing bTreeInfo'); + end; +// if not vfs^.fClose() then runError; +// if not (vfs^.fOpen(filename, info^.bNodeSize)) then runError; + end; + + case info^.treeType of + BT_CUSTOM : InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + BT_PCHAR : InstallUserFunctions(compareKeyPChar, copyKeyPChar, keySizePChar); + BT_STRING : InstallUserFunctions(compareKeyString, copyKeyString, keySizeString); + BT_SINGLE : InstallUserFunctions(compareKeySingle, copyKeySingle, keySizeSingle); + BT_DOUBLE : InstallUserFunctions(compareKeyDouble, copyKeyDouble, keySizeDouble); + BT_INT32 : InstallUserFunctions(compareKeyInt32, copyKeyInt32, keySizeInt32); + BT_INT64 : InstalluserFunctions(compareKeyInt64, copyKeyInt64, keySizeInt64); + else + InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + end; // case + treeChanged := FALSE; // The superblock of the tree hasn't been modified +end; // bTree.init + +constructor bTree.open; +var + lResult:longint; +label safeExit; +begin + info := NIL; + memoryTree := FALSE; + treeChanged := FALSE; // The superblock of the tree hasn't been modified + treeName := filename; + InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + //if (filename = '') or (fsearch(filename, '') = '') then begin writeln(filename, ' does not exist'); exit; end; + + // allocate the info section + vfs := virtualFS; + + if (vfs = NIL) then vfs := new(PStandardVFS, init); + if not vfs^.fExist(filename) then + begin + writeln('in bTree.open(): file does not exist'); + goto safeExit; + end; + new(info); + fillchar(info^, sizeof(info^), 0); + if not vfs^.fOpen(filename) then runError; + if not vfs^.fRead(info^, sizeof(BTreeInfo)) then runError; + if (info^.magic <> B_NODE_TREEINFO) then runError; +// if not vfs^.fClose() then runError; +// vfs^.fOpen(filename, info^.bNodeSize); + + case info^.treeType of + BT_CUSTOM : InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + BT_PCHAR : InstallUserFunctions(compareKeyPChar, copyKeyPChar, keySizePChar); + BT_STRING : InstallUserFunctions(compareKeyString, copyKeyString, keySizeString); + BT_SINGLE : InstallUserFunctions(compareKeySingle, copyKeySingle, keySizeSingle); + BT_DOUBLE : InstallUserFunctions(compareKeyDouble, copyKeyDouble, keySizeDouble); + BT_INT32 : InstallUserFunctions(compareKeyInt32, copyKeyInt32, keySizeInt32); + BT_INT64 : InstalluserFunctions(compareKeyInt64, copyKeyInt64, keySizeInt64); + end; // case +safeExit: +end; // bTree.open + +function bTree.align; +begin + result := ((sizeof(uptr) + keyLength + 7) shr 3) shl 3; +end; {bTree.align} + +function bTree.allocEmptyNode; +var newNode:PbNode; +begin + saveSuperBlock(); + setNull(result); + + if (not isNull(getFirstDeleted())) then + begin + newNode := loadPage(getFirstDeleted()); + if (newNode = NIL) then exit; + setFirstDeleted(newNode^.parent); + end + else + begin + getmem(newNode, info^.bNodeSize); + fillchar(newNode^, info^.bNodeSize, 0); + setNodes(getNodes() + 1); +// inc(info^.nodes); + newNode^.tag := getNodes(); + end; + + with newNode^ do + begin + magic := B_NODE_LEAF; + numKeys := 1; + size := dword(@newNode^.data) - dword(newNode); + setNull(left); + setNull(right); + setNull(parent); + setNull(reserved); + end; {with} + result := getAddress(newNode); + savePage(newNode); + discardPage(newNode); +// loadSuperBlock(); +end; {bTree.allocEmptyNode} + +function bTree.calcSize; +var + size:integer; + curNode:integer; + dataNode:PbNodeData; +begin + if (node = NIL) then exit; + dataNode := @node^.data; + size := dword(dataNode) - dword(node); + + for curNode := 1 to node^.numKeys do + begin + inc(size, align(keySize(@dataNode^.key))); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + if (node^.magic = B_NODE_INDEX) then inc(size, sizeof(uPtr)); + result := size; +end; + +procedure bTree.deAllocNode; +begin + if (node = NIL) then exit; + + with node^ do + begin + magic := B_NODE_OPEN; + setNull(left); + setNull(right); + setNull(reserved); + if not memoryTree then parent := getFirstDeleted; + + size := dword(@node^.data) - dword(node); + fillchar(data, info^.bNodeSize - size, 0); + end; + if not memoryTree then setFirstDeleted(getAddress(node)); + savePage(node); + freemem(node, info^.bNodeSize); +end; {btree.deAllocNode} + +function bTree.deleteEntry; +var + bnode, tmpNode, neighbor, parent : PbNode; + neighborSize:integer; + + dataNode, nextDataNode, tmpDataNode:PbNodeData; + src, dst : pointer; + tmpLink:uPtr; + nodeSize:uInt32; + curNode, res, size: integer; + tmpTag:uInt32; + found : boolean; + + procedure getNeighbor; + var + l, r:PbNode; + begin + neighbor := NIL; + neighborSize := 0; + l := loadPage(bnode^.left); + r := loadPage(bnode^.right); + + if (l = NIL) then + begin + // this next check may be unecessary + if (r^.parent = bnode^.parent) then + begin + neighbor := r; + neighborSize := r^.size; + end; + exit + end; + + if (r = NIL) then + begin + // this next check may be unecessary + if (l^.parent = bnode^.parent) then + begin + neighbor := l; + neighborSize := l^.size; + end; + exit + end; + + if (l^.parent = r^.parent) then // left/right neighbors are children of the same parent + begin + if (l^.size < r^.size) then + begin + neighbor := l; + neighborSize := l^.size; + discardPage(r); + exit + end + else + begin + neighbor := r; + neighborSize := r^.size; + discardPage(l); + exit + end; + end + else // one of the neighbors has a different parent + begin + if (l^.parent = bnode^.parent) then + begin + neighbor := l; + neighborSize := l^.size; + discardPage(r); + exit + end + else + begin + neighbor := r; + neighborSize := r^.size; + discardPage(l); + exit + end; + end; + discardPage(l); + discardPage(r); + end; {getNeighbor local function} + +begin + + setNull(result); + + if (isNull(u)) then exit; + bnode := loadPage(u); + + if (bnode^.numKeys = 0) then runError; {this should never happen} + + if (bnode^.magic = B_NODE_LEAF) then + begin + + { case info^.treeType of + BT_STRING:writeln(f, 'Deleting ', string(key^), ' size: ', align(keySize(key))); + BT_PCHAR:writeln(f, 'Deleting ', pchar(key)); + end;} + + dataNode := @bnode^.data; + curNode := 0; + repeat + res := compareKeys(@dataNode^.key, key); + if (res >= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until (curNode = bnode^.numKeys); + + if (res <> 0) then + begin +// writeln(f, 'Could not find in leaf node'); + discardPage(bnode); + exit + end; + + // Since we found the entry, we will have to save the + // superblock at the end. Set treeChanged to TRUE + treeChanged := TRUE; + result := dataNode^.link; + + nodeSize := align(keySize(@dataNode^.key)); + + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); + + move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode))); + dec(bnode^.numKeys); + dec(bnode^.size, nodeSize); + dataNode := pointer(dword(bnode) + bnode^.size); + fillchar(dataNode^, nodeSize, 0); + setKeys(getKeys() - 1); +// dec(info^.keys); + + // check for merges and other stuff + if (getAddress(bnode) = getRoot()) then + begin + if (bnode^.numKeys = 0) then + begin + setNull(info^.root); + setRoot(info^.root); + setHeight(getHeight() - 1); + //dec(info^.height); + deAllocNode(bnode); + exit; + end; + end + else // not root + begin + if (bnode^.size < (info^.bNodeSize / 2)) then + begin + getNeighbor; + if (neighbor <> NIL) and (neighborSize <> 0) then + begin + if (bnode^.size + neighborSize <= info^.bNodeSize) then + begin +//writeln(f, 'Merging leaf pages'); + // if neighbor is a predecessor of bnode then swap them + if (getAddress(neighbor) = bnode^.left) then + begin + tmpNode := bnode; + bnode := neighbor; + neighbor := tmpNode; + end; + // merge the two nodes together + // bnode is the left node, neighbor is the right node + src := @neighbor^.data; + dst := pointer(dword(bnode) + bnode^.size); + + size := neighbor^.size - (dword(src) - dword(neighbor)); + + move(src^, dst^, size); + inc(bnode^.size, size); + inc(bnode^.numKeys, neighbor^.numKeys); + setRight(bnode^.left, getAddress(neighbor)); + bnode^.right := neighbor^.right; + tmpTag := neighbor^.tag; // save tag info + move(bnode^, neighbor^, info^.bNodeSize); + neighbor^.tag := tmpTag; // restore tag info + savePage(neighbor); + deleteEntry(bnode^.parent, NIL, getAddress(bnode)); + discardPage(neighbor); + deAllocNode(bnode); + exit; + end //if (bnode^.size + neighborSize <= info^.bNodeSize) + else // "acquire" an entry from a neighbor + begin + + if (getAddress(neighbor) = bnode^.right) then + begin +//writeln(f, 'acquiring from leaf bnode/neighbor'); + // neighbor node is to the right. Append an entry from it to bnode + dataNode := @neighbor^.data; + src := dataNode; + dst := pointer(dword(bnode) + bnode^.size); + nodeSize := align(keySize(@dataNode^.key)); + move(src^, dst^, nodeSize); + inc(bnode^.size, nodeSize); + inc(bnode^.numKeys); + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); + move(nextDataNode^, + dataNode^, + neighbor^.size - (dword(dataNode) - dword(neighbor)) - nodeSize); + dec(neighbor^.size, nodeSize); + dec(neighbor^.numKeys); + + // clear off the end of the neighbor node + dataNode := pointer(dword(neighbor) + neighbor^.size); + fillchar(dataNode^, nodeSize, 0); + savePage(bnode); + savePage(neighbor); + dataNode := dst; + replaceEntry(bnode^.parent, @dataNode^.key, getAddress(bnode)); + end + else + begin +//writeln(f, 'acquiring from leaf neighbor/bnode'); + // neighbor node is to the left. Take the last entry and prepend to bnode + dataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + // dataNode (hopefully) now points to the last key/value pair in neighbor + nodeSize := align(keySize(@dataNode^.key)); + src := @bnode^.data; + dst := src; + inc(dst, nodeSize); + move(src^, dst^, bnode^.size - (dword(src) - dword(bnode))); + dst := src; + src := dataNode; + move(src^, dst^, nodeSize); + fillchar(src^, nodeSize, 0); + dec(neighbor^.size, nodeSize); + dec(neighbor^.numKeys); + inc(bnode^.size, nodeSize); + inc(bnode^.numKeys); + savePage(bnode); + savePage(neighbor); + // this can be optimized by storing the previous + // node from the for loop above + dataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + replaceEntry(neighbor^.parent, @dataNode^.key, getAddress(neighbor)); + end; // else + discardPage(bnode); + discardPage(neighbor); + exit; + + end; + + end; //if (neighbor <> NIL) and (neighborSize <> 0) + discardPage(neighbor); + end; // if (bnode^.size < (info^.bNodeSize / 2)) + end; {else not root} + + end + else {not a leaf} + begin + dataNode := @bnode^.data; + nextDataNode := dataNode; + nodeSize := align(keySize(@dataNode^.key)); + inc(nextDataNode, nodeSize); + found := FALSE; + for curNode := 1 to bnode^.numKeys do + begin + found := (dataNode^.link = value); + if (found) then break; + if (curNode <> bnode^.numKeys) then + begin + dataNode := nextDataNode; + nodeSize := align(keySize(@nextDataNode^.key)); + inc(nextDataNode, nodeSize); + end; + end; + + if (not found) then + begin + if (nextDataNode^.link = value) then + nextDataNode^.link := dataNode^.link + else + begin + // writeln(f, 'Could not find in index node'); + discardPage(bnode); + exit; + end; + end; + {if (dataNode = nextDataNode) then inc(nextDataNode, align(keySize(@dataNode^.key)));} + // Copy this link into the next link so we can just delete this node + {nextDataNode^.link := dataNode^.link;} + + fillchar(dataNode^, nodeSize, 0); + move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode))); + + dec(bnode^.numKeys); + dec(bnode^.size, nodeSize); + dst := pointer(dword(bnode) + bnode^.size); + fillchar(dst^, nodeSize, 0); + // check for merges and other stuff + if (getAddress(bnode) = getRoot()) then + begin + if (bnode^.numKeys = 0) then + begin + dataNode := @bnode^.data; + //info^.root := dataNode^.link; + setRoot(dataNode^.link); + setHeight(getHeight() - 1); + //dec(info^.height); + deAllocNode(bnode); + ///bnode := loadPage(info^.root); + bnode := loadPage(getRoot()); + setNull(bnode^.parent); + savePage(bnode); + discardPage(bnode); + exit + end; + end + else {not root} + begin + if (bnode^.size < (info^.bNodeSize / 2)) then + begin + getNeighbor; + if (neighbor<>NIL) and (neighborSize<>0) then + begin + if (bnode^.size + neighborSize <= info^.bNodeSize) then + begin +//writeln(f, 'merging index pages'); + // if neighbor is a predecessor of bnode then swap them + if (getAddress(neighbor) = bnode^.left) then + begin + tmpNode := bnode; + bnode := neighbor; + neighbor := tmpNode; + end; + // We need to get the key that currently resides between + // the bnode (left) link and the neighbor (right) link in the parent + parent := loadPage(bnode^.parent); + if (parent = NIL) then runError; // this would be bad + + dataNode := @parent^.data; + for curNode := 0 to parent^.numKeys-1 do + begin + if (dataNode^.link = getAddress(bnode)) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + // theoretically dataNode^.key is the key between the two links + + size := align(keySize(@dataNode^.key)); + + if ((bnode^.size + neighbor^.size + size - (dword(@bnode^.data) - dword(bnode))) > info^.bNodeSize) then + begin + // The key wouldn't fit. bail + discardPage(parent); + savePage(neighbor); + savePage(bnode); + discardPage(neighbor); + discardPage(bnode); + exit; + end; + + // copy the key to the end of bnode + src := @dataNode^.key; + dst := pointer(dword(bnode) + bnode^.size); + move(src^, dst^, keySize(@dataNode^.key)); + inc(bnode^.numKeys); // we just added a key + + dec(size, sizeof(uPtr)); + inc(bnode^.size, size); // increment by aligned size + inc(dst, size); // also increment the dst by aligned size + + discardPage(parent); // don't need the parent any more + // merge the two nodes together + // bnode is the left node, neighbor is the right node + src := @neighbor^.data; + // dst := pointer(dword(bnode) + bnode^.size); // set above + size := neighbor^.size - (dword(src) - dword(neighbor)); + move(src^, dst^, size); + inc(bnode^.size, size); + inc(bnode^.numKeys, neighbor^.numKeys); + + dataNode := @bnode^.data; + for curNode := 0 to bnode^.numKeys do + begin + setParent(dataNode^.link, getAddress(neighbor)); + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + + setRight(bnode^.left, getAddress(neighbor)); + bnode^.right := neighbor^.right; + tmpTag := neighbor^.tag; // save tag info + move(bnode^, neighbor^, info^.bNodeSize); + neighbor^.tag := tmpTag; // restore tag info + savePage(neighbor); + deleteEntry(bnode^.parent, NIL, getAddress(bnode)); + deAllocNode(bnode); + discardPage(neighbor); + + exit; + + end + else // "acquire" an entry from a neighbor + begin + // we need to do one extra check + if (getAddress(neighbor) = bnode^.right) then + begin + tmpLink := getAddress(bnode); + tmpDataNode := @neighbor^.data; + end + else + begin + tmpLink := getAddress(neighbor); + tmpDataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@tmpDataNode^.key))); + end; + + if (neighbor^.size - align(keySize(@tmpDataNode^.key)) < info^.bNodeSize / 2) then + begin + savePage(bnode); + discardPage(bnode); + discardPage(neighbor); + exit + end; + + // get the key between the neighbor/bnode pair + + parent := loadPage(bnode^.parent); + if (parent = NIL) then runError; // this would be bad + + dataNode := @parent^.data; + for curNode := 1 to parent^.numKeys do + begin + if (dataNode^.link = tmpLink) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + // theoretically dataNode^.key is the key between the two links + + nodeSize := align(keySize(@dataNode^.key)); + + if (getAddress(neighbor) = bnode^.right) then + begin +//writeln(f, 'acquiring from index bnode/neighbor'); + savePage(neighbor); // hmm.. did we change neighbor? + + src := @dataNode^.key; + dataNode := @neighbor^.data; + savePage(bnode); + insertEntry(getAddress(bnode), src, dataNode^.link); + replaceEntry(getAddress(parent), @dataNode^.key, getAddress(bnode)); + deleteEntry(getAddress(neighbor), NIL, dataNode^.link); + + end + else // neighbor is to the left, bnode is to the right + begin +//writeln(f, 'acquiring from index neighbor/bnode'); + + // We need to take the key from the parent node, coupled with the + // last value in the neighbor, and insert them into bnode + // To do this we have to move everything in bnode over + src := @dataNode^.key; + nodeSize := align(keySize(@dataNode^.key)); + dataNode := @bnode^.data; + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); + move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bnode))); + + // copy in the key + fillchar(dataNode^, nodeSize, 0); + move(src^, dataNode^.key, keySize(src)); + // now we update the size and keys + inc(bnode^.size, nodeSize); + inc(bnode^.numKeys); + // we still have to insert the value and update the child's parent ptr + tmpDataNode := dataNode; + + dataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + nextDataNode := dataNode; + inc(nextDataNode, align(keySize(@dataNode^.key))); + // insert the value + tmpDataNode^.link := nextDataNode^.link; + setParent(tmpDataNode^.link, getAddress(bnode)); + // need to change the insert (don't forget to update the child's parent ptr) + // insertEntry(getAddress(bnode), src, nextDataNode^.link); + savePage(bnode); + savePage(neighbor); + replaceEntry(getAddress(parent), @dataNode^.key, getAddress(neighbor)); + deleteEntry(getAddress(neighbor), NIL, nextDataNode^.link); + end; + discardPage(bnode); + discardPage(neighbor); + discardPage(parent); + + exit; + end; + discardPage(neighbor); + end; // if (bnode^.size + neighborSize <= info^.bNodeSize) then + + end; // if (bnode^.size < (info^.bNodeSize / 2)) then + + end; // else not root + + end; // not a leaf + + savePage(bnode); + discardPage(bnode); +end; {bTree.deleteEntry} + +procedure bTree.discardPage; +begin + // disk based trees allocate temporary memory to hold a page. So, if we're not + // a memory tree, dispose of the temp page + if not (memoryTree) then if (node <> NIL) then freemem(node, info^.bNodeSize); +end; {bTree.discardPage} + +function bTree.getAddress; +var u:uPtr; +begin + setNull(u); + + if (memoryTree) then + u.bPtr := node + else + u.offset := node^.tag; { * bNodeSize;} + + result := u; +end; {bTree.getAddress} +(* +function bTree.getRoot; +begin + if (info = NIL) then + setNull(result) + else + result := info^.root; +end; // bTree.getRoot +*) +function bTree.insertEntry; +var + tmpUPtr, tmpRoot:uPtr; + dataNode, nextDataNode, prevDataNode:PbNodeData; + midKey:BTreeSearchRec; + keyLength, curNode, dataSize, res:integer; + bnode, newNode, rootNode:PbNode; + nodeSize:uInt32; +begin + result := FALSE; + if (key = NIL) then exit; + +{ case info^.treeType of + BT_STRING:writeln(f, 'Inserting ', string(key^)); + BT_PCHAR:writeln(f, 'Inserting ', pchar(key)); + end;} + + midKey.key := NIL; + midKey.keySize := 0; + + keyLength := keySize(key); + + if (keyLength = 0) or (keyLength > info^.bNodeSize) then exit; + + // The superblock will have to be updated when the tree is destructed + // set treeChanged to TRUE + treeChanged := TRUE; + + nodeSize := align(keyLength); + + if (isNull(getRoot())) then /// + begin +///write('1'); + (* + * This case should only occur once when a directory is first created. + *) + tmpRoot := allocEmptyNode(); +// info^.root := allocEmptyNode(); + if (isNull(tmpRoot)) then runError; + + rootNode := loadPage(tmpRoot); + ///info^.root := tmpRoot; + setRoot(tmpRoot); + setKeys(getKeys() + 1); + setHeight(getHeight() + 1); + //inc(info^.keys); + //inc(info^.height); + rootNode^.magic := B_NODE_LEAF; + dataNode := @rootNode^.data; + + fillchar(dataNode^, nodeSize, 0); + + dataNode^.link := value; + copyKey(key, @dataNode^.key); + + inc(rootNode^.size, nodeSize); {hm.. should I add the sizeof the link?} + + savePage(rootNode); + discardPage(rootNode); + result := TRUE; + exit + end; + + bnode := loadPage(u); + + if (bnode^.size + nodeSize <= info^.bNodeSize) then + begin +/// if not memoryTree then writeln('2'); + dataNode := @bnode^.data; + curNode := 0; + repeat + res := compareKeys(key, @dataNode^.key); + if (res <= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until curNode = bnode^.numKeys; + + if (res = 0) then + begin + dataNode^.link := value; + savePage(bnode); + discardPage(bnode); + result := TRUE; + exit; + end; + + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); {cast to byte size records} + + move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bNode))); + + fillchar(dataNode^, nodeSize, 0); + copyKey(key, @dataNode^.key); + + dataNode^.link := value; + + if (bnode^.magic = B_NODE_INDEX) then + begin + if (not isNull(dataNode^.link)) then setParent(dataNode^.link, getAddress(bnode)); + { if (curNode = bnode^.numKeys) then + begin} + tmpUPtr := dataNode^.link; + dataNode^.link := nextDataNode^.link; + nextDataNode^.link := tmpUPtr; + { end;} + end; + inc(bnode^.numKeys); + + if (bnode^.magic = B_NODE_LEAF) then setKeys(getKeys() + 1); //inc(info^.keys); + inc(bNode^.size, nodeSize); + savePage(bnode); + end + else + begin + newNode := loadPage(allocEmptyNode()); + if (newNode = NIL) then runError; + + if not memoryTree then + begin + // If we're not a memory tree, then we should reload the page. + // This allows the B^Tree to be re-entrant. + discardPage(bnode); + bnode := loadPage(u); // reload it, because it could have changed? + end; + + newNode^.magic := bnode^.magic; + + newNode^.left := getAddress(bnode); + newNode^.right := bnode^.right; + newNode^.parent := bnode^.parent; + + if not(isNull(newNode^.right)) then setLeft(newNode^.right, getAddress(newNode)); + bnode^.right := getAddress(newNode); + + dataNode := @bnode^.data; + + {for curNode:=0 to (bnode^.numKeys div 2)-1 do + inc(dataNode, align(keySize(@dataNode^.key)));} + curNode := 1; + repeat + prevDataNode := dataNode; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until (dword(dataNode) - dword(bnode) >= bnode^.size shr 1); + + {midKey := dataNode;} + dataNode := prevDataNode; + midKey.keySize := keySize(@dataNode^.key); + GetMem(midKey.key, midKey.KeySize); + {if (midKey.key = NIL) then halt;} + copyKey(@dataNode^.key, midKey.key); + + dec(curNode); + + if (bnode^.magic = B_NODE_LEAF) then + begin + + inc(dataNode, align(keySize(@dataNode^.key))); + + dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); + move(dataNode^, newNode^.data, dataSize); + fillchar(dataNode^, dataSize, 0); + + dec(bnode^.size, dataSize); + newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); + + newNode^.numKeys := bnode^.numKeys - curNode; + + dec(bnode^.numKeys, newNode^.numKeys); + + savePage(bnode); + savePage(newNode); + + if memoryTree then /// <-- new check (re-entrancy) + begin + // The following code was original, but in order to allow for re-entrancy on + // disk based trees, we have to insert later (see last insertEntry() call in this + // function) +/// original code: + if (compareKeys(key, midKey.key) <= 0) then + insertEntry(getAddress(bnode), key, value) + else + insertEntry(getAddress(newNode), key, value); +/// end original code + end; + + + end + else // not a leaf + begin + inc(dataNode, align(keySize(@dataNode^.key))); + + dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); + move(dataNode^, newNode^.data, dataSize); + + fillchar(dataNode^, dataSize, 0); + + dec(bnode^.size, dataSize); + // we need to adjust the size because we're taking out the midkey + // and we're also adding that extra pointer + dec(bnode^.size, align(keySize(midkey.key))); + inc(bnode^.size, sizeof(uPtr)); + + newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); + + newNode^.numKeys := bnode^.numKeys - curNode; + dec(bnode^.numKeys, newNode^.numKeys); + dec(bnode^.numKeys); + + savePage(bnode); + savePage(newNode); + + dataNode := @newNode^.data; + for curNode := 0 to newNode^.numKeys do + begin + setParent(dataNode^.link, getAddress(newNode)); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + //savePage(bnode); + + if (compareKeys(key, midKey.key) <= 0) then + insertEntry(getAddress(bnode), key, value) + else + insertEntry(getAddress(newNode), key, value); + + end; + + (* + * If we were the root node, then create a new one + *) + + if (getAddress(bnode) = getRoot()) then /// + begin + tmpRoot := allocEmptyNode(); +// info^.root := allocEmptyNode; + rootNode := loadPage(tmpRoot); + ///info^.root := tmpRoot; + setRoot(tmpRoot); + setParent(getAddress(bnode), getRoot()); /// + setParent(getAddress(newNode), getRoot()); /// + + setHeight(getHeight() +1); + //inc(info^.height); + {dataNode := @newNode^.data; } + dataNode := @rootNode^.data; + with rootNode^ do + begin + magic := B_NODE_INDEX; + {numKeys := 1;} + dataNode^.link := getAddress(bnode); + + keyLength := midKey.keySize; + copyKey(midKey.key, @dataNode^.key); + inc(size, align(keyLength)); + inc(size, sizeof(uPtr)); + inc(dataNode, align(keyLength)); + dataNode^.link := getAddress(newNode); + + end; {with} + savePage(rootNode); + discardPage(rootNode); + end {if oldNode = root} + else + begin + insertEntry(bnode^.parent, midKey.key, getAddress(newNode)); + end; + + if not(memoryTree) and (bNode^.magic = B_NODE_LEAF) then insertEntry(findLeafNode(getRoot(), key), key, value); /// <- Allows re-entrancy + if (midKey.key <> NIL) and (midKey.keySize <> 0) then FreeMem(midKey.key, midKey.keySize); + discardPage(newNode); + end; {if out of space in this node} + discardPage(bnode); + result := TRUE +end; {bTree.insertEntry} + +function bTree.isNull; +begin + {if (memoryTree) then + result := (u.bPtr = NIL) + else} + result := (u.offset = 0) +end; {bTree.isNull} + +function bTree.loadPage; +var + node:PbNode; +begin + result := NIL; + if isNull(u) then exit; + + if (memoryTree) then + node := u.bPtr + else + begin + if not vfs^.fSeek(dword(u.offset) * info^.bNodeSize) then runError; {exit} + getmem(node, info^.bNodeSize); + if (node = NIL) then exit; + if not vfs^.fRead(node^, info^.bNodeSize) then runError; {exit;} + end; // !memoryTree + + result := node +end; // bTree.loadPage + +procedure bTree.loadSuperBlock; +begin + if memoryTree then exit; + if not vfs^.fSeek(0) then writeln('Error seeking in bTree.loadSuperBlock'); + if not vfs^.fRead(info^, sizeof(BTreeInfo)) then writeln('Error reading in bTree Info in bTree.loadSuperBlock'); +end; + +function bTree.replaceEntry; +var + dataNode:PbNodeData; + node:PbNode; + curNode, res, srcKeySize, dstKeySize, deltaSize:integer; + src, dst:pointer; +begin + result := FALSE; + if ((key = NIL) or (isNull(u))) then exit; + node := loadPage(u); + if (node = NIL) then exit; + + if (node^.magic <> B_NODE_INDEX) then + begin + writeln('Error! Not in index nodes.'); + discardPage(node); + exit + end; + + dataNode := @node^.data; + // possible bug on count + for curNode := 0 to node^.numKeys do + begin + if (dataNode^.link = value) then + begin + srcKeySize := keySize(key); + dstKeySize := keySize(@dataNode^.key); + deltaSize := align(srcKeySize) - align(dstKeySize); + if (deltaSize = 0) then + begin + // source and dest keys are equal length. Replace old key with new key + fillchar(dataNode^.key, dstKeySize, 0); + move(key^, dataNode^.key, srcKeySize); + savePage(node); + end + else + if (deltaSize < 0) then + begin + // source key is smaller than dest key. + fillchar(dataNode^.key, dstKeySize, 0); + move(key^, dataNode^.key, srcKeySize); + src := dataNode; + dst := dataNode; + inc(src, align(dstKeySize)); // no, this isn't backwards + inc(dst, align(srcKeySize)); // we're copying from the old size + move(src^, dst^, node^.size - (dword(src) - dword(node))); + inc(node^.size, deltaSize); // deltaSize is negative + savePage(node); + end + else + begin + // source key is bigger than dest key + if ((deltaSize + node^.size) > info^.bNodeSize) then + begin +// writeln(f, 'Source key would cause split'); + end + else // source key will fit + begin + // dataNode points to the value/key pair + src := dataNode; + dst := dataNode; + inc(src, align(dstKeySize)); + inc(dst, align(srcKeySize)); + move(src^, dst^, node^.size - (dword(src) - dword(node))); + fillchar(dataNode^.key, dstKeySize, 0); + move(key^, dataNode^.key, srcKeySize); + inc(node^.size, deltaSize); + savePage(node); + end; + + end; + + break; + end; + inc(dataNode, align(keySize(@dataNode^.key))); + end; + discardPage(node); + result := TRUE; +end; {bTree.replaceEntry} + +procedure bTree.savePage; +begin + if (memoryTree) then exit; + + if (node = NIL) then exit; + + if not vfs^.fSeek(dword(node^.tag) * info^.bNodeSize) then writeln('Error seeking in bTree.savePage()'); + if not vfs^.fWrite(node^, info^.bNodeSize) then writeln('Error writing page in bTree.savePage()'); +end; // bTree.savePage + +procedure bTree.saveSuperBlock; +begin + if (not(memoryTree)) then + begin +// if not vfs^.fClose() then writeln('Error calling fClose (for the first time) file in bTree.done()'); +// if not vfs^.fOpen(treeName, 1) then writeln('Error calling fOpen in bTree.done()'); + if not vfs^.fSeek(0) then writeln('Error seeking in bTree.saveSuperBlock()'); // reset back to the beginning + if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error saving superblock in bTree.saveSuperBlock()'); + // disposing the vfs pointer will close the file as necessary + end; + +end; // bTree.saveSuperBlock + +procedure bTree.setLeft; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.left := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setLeft} + +procedure bTree.setParent; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.parent := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setParent} + +procedure bTree.setNull; +begin +{ if (memoryTree) then} + u.offset := 0 + {else + u.offset := -1;} +end; {bTree.setNull} + +procedure bTree.setRight; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.right := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setRight} + +function bTree.fetchFirstKey; +var + node:PbNode; + dataNode:PbNodeData; +begin + result := FALSE; + if isNull(u) then exit; + node := loadPage(u); + if (node = NIL) then exit; + + dataNode := @node^.data; + if (node^.magic = B_NODE_LEAF) then + begin + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + if (searchRec.key = NIL) then exit; + + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + + result := TRUE; + end + else + begin + result := fetchFirstKey(dataNode^.link, searchRec); + end; + discardPage(node); +end; {bTree.fetchFirstKey} + +function bTree.fetchLastKey; +var + node:PbNode; + dataNode:PbNodeData; + curNode:integer; +begin + result := FALSE; + if isNull(u) then exit; + node := loadPage(u); + if (node = NIL) then exit; + + dataNode := @node^.data; + + if (node^.magic = B_NODE_LEAF) then + begin + for curNode := 1 to node^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + if (searchRec.key = NIL) then exit; + + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + result := TRUE; + end + else + begin + for curNode := 1 to node^.numKeys do + inc(dataNode, align(keySize(@dataNode^.key))); + result := fetchLastKey(dataNode^.link, searchRec); + end; + discardPage(node); +end; {bTree.fetchLastKey} + +procedure bTree.findLeafNode; +var + bNode:PbNode; + dataNode:PbNodeData; + i:integer; + link:uPtr; +begin + setNull(result); + if (key = NIL) then exit; + + if (isNull(u)) then exit; + + bnode := loadPage(u); + + if (bnode^.magic = B_NODE_LEAF) then + begin + result := u; + discardPage(bnode); + exit; + end + else + begin + dataNode := @bnode^.data; + for i:=0 to bnode^.numKeys -1 do + begin + if (compareKeys(key, @dataNode^.key) <= 0) then + begin + link := dataNode^.link; + discardPage(bnode); + result := findLeafNode(link, key); + exit; + end; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + link := dataNode^.link; + discardPage(bnode); + result := findLeafNode(link, key); + end; +end; {bTree.findLeafNode} + +procedure bTree.freeAll; +begin + if not(memoryTree) then exit; + freePage(getRoot()); + setNull(info^.root); +end; {bTree.freeAll} + +procedure bTree.freePage; +var + curNode:integer; + dataNode:PbNodeData; + node : PbNode; +begin + + if isNull(u) then exit; + + node := u.bPtr; + curNode := 0; + if (node^.magic = B_NODE_INDEX) then + begin + dataNode := @node^.data; + for curNode := 0 to node^.numKeys-1 do + begin + freePage(dataNode^.link); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + freePage(dataNode^.link); + end; + freemem(node, info^.bNodeSize); +end; // bTree.freePage + +function bTree.Delete; +begin + result := deleteEntry(findLeafNode(getRoot(), key), key, value); +end; {bTree.Delete} + +function bTree.Find; +var + bnode:PbNode; + dataNode:PbNodeData; + i, res:integer; + +begin + result := FALSE; + setNull(u); + + bnode := loadPage(findLeafNode(getRoot(), key)); + if (bnode = NIL) then exit; {oops! serious problem} + + if (bnode^.magic <> B_NODE_LEAF) then writeln('Error! not in leaf nodes'); + + dataNode := @bnode^.data; + + for i:=0 to bnode^.numKeys-1 do + begin + res := compareKeys(@dataNode^.key, key); // strcomp(@dataNode^.key.str[1], pchar(key)); + if (res >= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for i} + + // did we find it? + if (res = 0) then + begin + u := dataNode^.link; + result := TRUE; + end; {if res = 0} + + discardPage(bnode); +end; {bTree.Find} + +procedure bTree.FindClose; +begin + with searchRec do + begin + setNull(value); + if (key<>NIL) then + FreeMem(key, align(keySize)); + key := NIL; + keySize := 0; + end; {with F} +end; {bTree.FindClose} + +function bTree.FindFirst; +var + node:PbNode; + dataNode:PbNodeData; + curNode, res:integer; +begin + result := FALSE; + + with searchRec do + begin + setNull(value); + key := NIL; + keySize := 0 + end; {with searchRec} + if (key = NIL) then exit; + node := loadPage(findLeafNode(getRoot(), key)); + if (node = NIL) then exit; + + dataNode := @node^.data; + curNode := 0; + repeat + res := compareKeys(key, @dataNode^.key); + if (res <= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until curNode = node^.numKeys; + + if (res = 0) then // we found it + begin + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + fillchar(searchRec.key^, align(searchRec.keySize), 0); + if (searchRec.key = NIL) then exit; + copyKey(@dataNode^.key, searchRec.key); + result := TRUE; + end; {if res = 0} + + discardPage(node); +end; {bTree.FindFirst} + +function bTree.FindNext; +var + nextNode:uPtr; + node:PbNode; + dataNode:PbNodeData; + curNode, res:integer; + +begin + result := FALSE; + + if (searchRec.key = NIL) then exit; + + node := loadPage(findLeafNode(getRoot(), searchRec.key)); + if (node = NIL) then + begin + findClose(searchRec); + exit + end; + + dataNode := @node^.data; + curNode := 0; + repeat + res := compareKeys(@dataNode^.key, searchRec.key); + if (res > 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until curNode = node^.numKeys; + + if (res <= 0) and (curNode = node^.numKeys) then + begin + nextNode := node^.right; + discardPage(node); + node := loadPage(nextNode); + if (node = NIL) then + begin + findClose(searchRec); + exit + end; {node = NIL} + dataNode := @node^.data; + end; + + FreeMem(searchRec.key, align(searchRec.keySize)); + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + searchRec.key := NIL; + GetMem(searchRec.key, align(searchRec.keySize)); + // sanity check + if (searchRec.key <> NIL) then + begin + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + result := TRUE + end; + + discardPage(node); +end; {bTree.FindNext} + +function bTree.FindPrev; +var + prevNode:uPtr; + node:PbNode; + lastDataNode, dataNode:PbNodeData; + curNode, res:integer; + +begin + result := FALSE; + if (searchRec.key = NIL) then exit; + + node := loadPage(findLeafNode(getRoot(), searchRec.key)); + if (node = NIL) then + begin + findClose(searchRec); + exit + end; + + dataNode := @node^.data; + lastDataNode := dataNode; + curNode := 0; + res := compareKeys(searchRec.key, @dataNode^.key); + + if (res > 0) then + repeat + lastDataNode := dataNode; + inc(curNode); + inc(dataNode, align(keySize(@dataNode^.key))); + res := compareKeys(searchRec.key, @dataNode^.key); + until (res <= 0) or (curNode = node^.numKeys); + + dataNode := lastDataNode; + + if ((res <= 0) and (curNode = 0)) then + begin + prevNode := node^.left; + discardPage(node); + node := loadPage(prevNode); + if (node = NIL) then + begin + FindClose(searchRec); + exit + end; + dataNode := @node^.data; + // scan to the end of the previous node. + // If there's only 1 key, this will do nothing + for curNode := 1 to node^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + end; + + FreeMem(searchRec.key, align(searchRec.keySize)); + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + searchRec.key := NIL; + GetMem(searchRec.key, align(searchRec.keySize)); + // sanity check + if (searchRec.key <> NIL) then + begin + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + result := TRUE + end; + + discardPage(node) +end; {bTree.FindPrev} + +function bTree.GetFirstKey; +begin + with searchRec do + begin + setNull(value); + key := NIL; + keySize := 0; + end; {with searchRec} + result := fetchFirstkey(getRoot(), searchRec); + +end; {bTree.GetFirstKey} + +function bTree.GetKeyCount; +begin + if (info <> NIL) then + result := getKeys() //info^.keys + else + result := 0 +end; // bTree.GetKeyCount + +function bTree.GetLastKey; +begin + with searchRec do + begin + setNull(value); + key := NIL; + keySize := 0; + end; {with searchRec} + result := fetchLastKey(getRoot(), searchRec); +end; {bTree.GetLastKey} + +procedure bTree.Print; +var + curNode:integer; + dataNode:PbNodeData; + node : PbNode; + link : uPtr; +begin + if isNull(u) then exit; + PrintInfo(f, u); + node := loadPage(u); + curNode := 0; + if (node^.magic = B_NODE_INDEX) then + begin + dataNode := @node^.data; + for curNode := 0 to node^.numKeys-1 do + begin + Print(f, dataNode^.link); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + Print(f, dataNode^.link); + end; + discardPage(node); +end; {bTree.Print} + +procedure bTree.PrintInfo; +const leafStr:array[boolean] of string[5] = ('INDEX', 'LEAF'); +var + dataNode:PbNodeData; + curNode:integer; + node : PbNode; + i:integer; +begin + node := loadPage(u); + if (node = NIL) then exit; + write(f, ' ', hex(dword(node^.parent.offset))); + writeln(f, ' ', leafStr[node^.magic = B_NODE_LEAF], ' (', hex(node^.magic), ')'); + write(f, hex(dword(node^.left.bPtr)), ' <-> ', hex(getAddress(node).u), ' <-> ', hex(dword(node^.right.bPtr))); + writeln(f, ' | capacity ', node^.size, '[', calcSize(node), '](', info^.bNodeSize, ') | keys:', node^.numKeys); + if (node^.numKeys = 0) then exit; + dataNode := @node^.data; + + curNode := 0; + for curNode := 0 to node^.numKeys-(ord(node^.magic = B_NODE_LEAF)) do + begin + + write(f, '| ', curNode:2, '| '); + + {if (node^.magic = B_NODE_INDEX) then} + write(f, '[', hex(dword(dataNode^.link.offset)), ']'); + + if (curNode <> node^.numKeys) then + case info^.treeType of + BT_custom:with TBlockRun(dataNode^.key) do writeln(f, ' {', AG, ':', start, ':', len, '} '); + BT_INT32:writeln(f, ' {', hex(dataNode^.key.i), '} '); + BT_INT64:writeln(f, ' {', hex(dataNode^.key.x), '} '); + BT_SINGLE:writeln(f, ' {', dataNode^.key.s, '} '); + BT_DOUBLE:writeln(f, ' {', dataNode^.key.d, '} '); + BT_STRING:writeln(f, ' {', dataNode^.key.str, '} '); + BT_PCHAR:begin + (*writeln(f, ' {', pchar(@dataNode^.key)^, '} '); *) + write(f, ' {'); + i:=0; + while (dataNode^.key.str[i]<>#0) do + begin + write(f, dataNode^.key.str[i]); + inc(i); + end; + writeln(f, '} '); + end; + end; + + inc(dataNode, align(keySize(@dataNode^.key))); + end; + discardPage(node); + writeln(f); +end; {bTree.PrintInfo} + +function bTree.Insert; +begin + result := insertEntry(findLeafNode(getRoot(), key), key, value); +end; {bTree.Insert} + +procedure bTree.InstallUserFunctions; +begin + compareKeys := cmpFunc; + copyKey := copyProc; + keySize := ksFunc; +end; {bTree.InstallUserFunc} + +procedure bTree.PrintList; +var + dataNode:PbNodeData; + node:PbNode; + link:uPtr; + i, j : integer; + f:text; +begin + + if (isNull(getRoot())) then exit; + assign(f, filename); + {$I-} rewrite(f); {$I+} + if (IOResult() <> 0) then exit; + + node := loadPage(getRoot()); + if (node = NIL) then exit; + while (node^.magic = B_NODE_INDEX) do + begin + dataNode := @node^.data; + link := dataNode^.link; + discardPage(node); + node := loadPage(link); + end; + + while (not isNull(link)) do + begin + dataNode := @node^.data; + for i := 0 to node^.numKeys -1 do + begin + case info^.treeType of + BT_CUSTOM:with TBlockRun(dataNode^.key) do writeln(f, AG, ':', start, ':', len); + BT_INT32:writeln(f, hex(dataNode^.key.i)); + // BT_INT64:writeln(f, dataNode^.key.x); + BT_SINGLE:writeln(f, dataNode^.key.s); + BT_DOUBLE:writeln(f, dataNode^.key.d); + BT_STRING:writeln(f, dataNode^.key.str); + BT_PCHAR:begin + j:=0; + while (dataNode^.key.str[j]<>#0) do + begin + write(f, dataNode^.key.str[j]); + inc(j); + end; + writeln(f); + end; + end; // case + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for i} + link := node^.right; + discardPage(node); + node := loadPage(link); + end; {while} + {$I-} close(f); {$I+} + if (IOResult() <> 0) then writeln('Error calling close() in bTree.PrintList'); +end; {bTree.PrintList} + +procedure bTree.PrintWholeTree; +var f:text; +begin + assign(f, filename); + {$I-} rewrite(f); {$I+} + if (IOResult() <> 0) then exit; + Print(f, getRoot()); + {$I-} close(f); {$I+} + if (IOResult() <> 0) then writeln('Error calling close() in bTree.PrintWholeTree'); +end; {bTree.PrintWholeTree} + +destructor bTree.done; +begin +// if (not isNull(getRoot())) then freeAll(); + if (memoryTree) then freePage(getRoot()); + if (info <> NIL) then dispose(info); + if (vfs <> NIL) then dispose(vfs, done); + info := NIL; + vfs := NIL; +end; // bTree.done + +constructor TQueue.init; +begin + queue := NIL; + elements := 0; + maxElements := elementCount; + size := elementSize; + head := 0; + tail := 0; + if (size <> 0) and (maxElements <> 0) then + begin + GetMem(queue, size*maxElements); + fillchar(queue^, size*maxElements, 0); + end; +end; // TQueue.init + +function TQueue.dequeue; +var p:pointer; +begin + result := FALSE; + if (queue = NIL) or (elements = 0) then exit; + p := queue; + inc(p, head*size); + head := (head+1) mod maxElements; + move(p^, item, size); + dec(elements); + result := TRUE; +end; // TQueue.dequeue + +function TQueue.enqueue; +var p:pointer; +begin + result := FALSE; + if (queue = NIL) or (elements = maxElements) then exit; + p := queue; + inc(p, tail*size); + move(item, p^, size); + tail := (tail+1) mod maxElements; + inc(elements); + result := TRUE; +end; // TQueue.enqueue + +function TQueue.getElementCount; result := elements; + +destructor TQueue.done; +begin + if (size <> 0) and (maxElements <> 0) then FreeMem(queue, size*maxElements); + queue := NIL; + elements := 0; + maxElements := 0; + size := 0; + head := 0; + tail := 0; +end; // TQueue.done + +end. \ No newline at end of file diff --git a/lists_backup.pas b/lists_backup.pas new file mode 100755 index 0000000..d183341 --- /dev/null +++ b/lists_backup.pas @@ -0,0 +1,2208 @@ +{$TPO-} // treat all pointer inc/dec's as byte adjusts +unit lists; + +interface + +type + int8 = shortInt; + int16 = smallInt; + int32 = longInt; + + uInt8 = byte; + uInt16 = word; + uInt32 = dWord; + +type + treeTypes = (BT_CUSTOM, BT_PCHAR, BT_STRING, BT_SINGLE, BT_DOUBLE, BT_INT32, BT_INT64); + PbNode = ^TbNode; + PbNodeData = ^TbNodeData; + + TBlockRun = packed record + AG : int32; + start : uInt16; + len : uInt16; + end; + + inodeAddr = TblockRun; + + uPtr = packed record + case integer of + 0: (i : int32); + 1: (u : uInt32); + 2: (s : single); + 3: (d : double); + 4: (p : pointer); + 5: (bPtr : PbNode); + 6: (offset : int64); + 7: (iAddr : inodeAddr); + end; + + PQueue = ^TQueue; + TQueue = object + private + queue:pointer; + elements:uInt32; + maxElements:uInt32; + size:uInt32; + head, tail:uInt32; + public + constructor init(elementCount, elementSize:uInt32); + function dequeue(var item):boolean; + function enqueue(var item):boolean; + function getElementCount:uInt32; + destructor done; + end; // TQueue + + PBTreeVFS = ^TBTreeVFS; + TBTreeVFS = object + public + constructor init; + function fClose:boolean; virtual; + function fCreate(const filename:string):boolean; virtual; + function fExist(const filename:string):boolean; virtual; + function fOpen(const filename:string):boolean; virtual; + function fRead(var buf; size:uInt32):boolean; virtual; + function fSeek(offset:uInt32):boolean; virtual; + function fWrite(var buf; size:uInt32):boolean; virtual; + destructor done; virtual; + end; // TBTreeVFS + + TbNode = packed record + magic : uInt32; + tag : uInt32; + numKeys : uInt32; + size : uInt32; + left : uPtr; + right : uPtr; + parent : uPtr; + reserved : uPtr; + data : array[0..0] of byte; + end; + + TbNodeData = packed record + link : uPtr; + key : record + case treeTypes of + BT_PCHAR, + BT_STRING: (str:string[1]); + BT_SINGLE: (s:single); + BT_DOUBLE: (d:double); + BT_INT32 : (i:int32); + BT_INT64 : (x:int64); + end; {record} + end; {TbNode} + +type + BTreeSearchRec = record + value : uPtr; + key : pointer; + keySize:integer; + end; {BTreeSearchRec} + +type + PBTreeInfo = ^BTreeInfo; + BTreeInfo = packed record + magic : int64; + root : uPtr; + firstDeleted : uPtr; + nodes : uInt32; + height : uInt32; + keys : uInt32; + bNodeSize : uInt32; + treeType : treeTypes; + end; {BTreeHeader} + +type compareKeyFunc = function(key1, key2:pointer):integer; +type copyKeyProc = procedure(srcKey, destKey:pointer); +type keySizeFunc = function(key:pointer):integer; + +type + PbTree = ^bTree; + bTree = object + private + treeName : string; + compareKeys : compareKeyFunc; + copyKey : copyKeyProc; + keySize : keySizeFunc; + info : PBTreeInfo; + vfs : PBTreeVFS; + //dataFile : file; + memoryTree : boolean; + treeChanged : boolean; + function align(keyLength:integer):integer; + function allocEmptyNode:uPtr; + function calcSize(node:PbNode):integer; + procedure deAllocNode(node:PbNode); + function deleteEntry(u:uPtr; key:pointer; value:uPtr):uPtr; + procedure discardPage(node:PbNode); + function fetchFirstKey(u:uPtr; var searchRec:BTreeSearchRec):boolean; + function fetchLastKey(u:uPtr; var searchRec:BTreeSearchRec):boolean; + function findLeafNode(u:uPtr; key:pointer):uPtr; + procedure freeAll; + procedure freePage(u:uPtr); + function getAddress(node:PbNode):uPtr; + function getRoot:uPtr; + function insertEntry(u:uPtr; key:pointer; value:uPtr):boolean; + function isNull(u:uPtr):boolean; + function loadPage(u:uPtr):PbNode; + procedure loadSuperBlock; + procedure Print(var f:text; u:uPtr); + procedure PrintInfo(var f:text; u:uPtr); + function replaceEntry(u:uPtr; key:pointer; value:uPtr):boolean; + procedure savePage(node:PbNode); + procedure saveSuperBlock; + procedure setLeft(u:uPtr; newPtr:uPtr); + procedure setNull(var u:uPtr); + procedure setRight(u:uPtr; newPtr:uPtr); + procedure setParent(u:uPtr; newPtr:uPtr); + + public + constructor init(const filename:string; nodeSize:uInt32; tt:treeTypes; virtualFS:PBTreeVFS); + constructor open(const filename:string; virtualFS:PBTreeVFS); + function Delete(key:pointer; value:uPtr):uPtr; + function Insert(key:pointer; value:uPtr):boolean; + procedure InstallUserFunctions(cmpFunc:compareKeyFunc; copyProc:copyKeyProc; ksFunc:keySizeFunc); + function Find(key:pointer; var u:uPtr):boolean; + procedure FindClose(var searchRec:BTreeSearchRec); + function FindFirst(key:pointer; var searchRec:BTreeSearchRec):boolean; + function FindNext(var searchRec:BTreeSearchRec):boolean; + function FindPrev(var searchRec:BTreeSearchRec):boolean; + function GetFirstKey(var searchRec:BTreeSearchRec):boolean; + function GetKeyCount:uInt32; + function GetLastKey(var searchRec:BTreeSearchRec):boolean; + procedure PrintList(const filename:string); + procedure PrintWholeTree(const filename:string); + destructor done; + end; // bTree + +implementation + +uses strings, dos; + +const B_NODE_LEAF = $4641454C; // LEAF +const B_NODE_INDEX = $58444E49; // INDX +const B_NODE_OPEN = $4E45504F; // OPEN +const B_NODE_TREEINFO = $4F464E4945455254; // TREEINFO + +type + PStandardVFS = ^TStandardVFS; + TStandardVFS = object(TBTreeVFS) + private + dataFile:file; + openFile:boolean; + public + constructor init; + function fClose:boolean; virtual; + function fCreate(const filename:string):boolean; virtual; + function fExist(const filename:string):boolean; virtual; + function fOpen(const filename:string):boolean; virtual; + function fRead(var buf; size:uInt32):boolean; virtual; + function fSeek(offset:uInt32):boolean; virtual; + function fWrite(var buf; size:uInt32):boolean; virtual; + destructor done; virtual; + end; // TStandardVFS + +// TVFS virtual abstract functions + +constructor TBTreeVFS.init; +begin + writeln('Don''t call the parent constructor'); +end; // TBTreeVFS.init + +function TBTreeVFS.fClose; result := FALSE; +function TBTreeVFS.fCreate; result := FALSE; +function TBTreeVFS.fExist; result := FALSE; +function TBTreeVFS.fOpen; result := FALSE; +function TBTreeVFS.fRead; result := FALSE; +function TBTreeVFS.fSeek; result := FALSE; +function TBTreeVFS.fWrite; result := FALSE; + +destructor TBTreeVFS.done; +begin + writeln('Don''t call the parent destructor'); +end; // TBTreeVFS.done + +// TStandardVFS methods + +constructor TStandardVFS.init; +begin + openFile := FALSE; +end; // TStandardVFS.init + +function TStandardVFS.fClose; +begin + if (openFile) then {$I-} close(dataFile); {$I+} + openFile := (IOResult() <> 0); + result := not openFile +end; // TStandardVFS.fClose + +function TStandardVFS.fCreate; +begin + assign(dataFile, filename); + {$I-} rewrite(dataFile, 1); {$I+} + openFile := (IOResult() = 0); + result := openFile +end; // TStandardVFS.fCreate + +function TStandardVFS.fExist; +begin + result := (fSearch(filename, '') <> '') +end; // TStandardVFS.fExist + +function TStandardVFS.fOpen; +begin + assign(dataFile, filename); + {$I-} reset(dataFile, 1); {$I+} + openFile := (IOResult() = 0); + result := openFile +end; // TStandardVFS.fOpen + +function TStandardVFS.fRead; +var lResult:uInt32; +begin + blockread(dataFile, buf, size, lResult); + result := ((IOResult() = 0) and (lResult = size)); +end; // TStandardVFS.fRead + +function TStandardVFS.fSeek; +begin + {$I-} seek(dataFile, offset); {$I+} + result := (IOResult() = 0) +end; // TStandardVFS.fSeek + +function TStandardVFS.fWrite; +var lResult:uInt32; +begin + // writeln('fWrite(dataFile, buf, ', count, ') @ position: ', filePos(dataFile)); + blockwrite(dataFile, buf, size, lResult); + result := ((IOResult() = 0) and (lResult = size)); +end; // TStandardVFS.fWrite + +destructor TStandardVFS.done; +begin + if (openFile) then fClose(); +end; // TStandardVFS.done + +function compareKeyNull(key1, key2:pointer):integer; result := 0; // compareKeyNull + +procedure copyKeyNull(srcKey, destKey:pointer); begin end; // copyKeyNull + +function keySizeNull(key:pointer):integer; result := 0; // keySizeNull + +(* + * CompareKey functions + * + *) + +function compareKeyString(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if string(key1^) < string(key2^) then + result := -1 + else + if string(key1^) > string(key2^) then result := 1; +end; {comapreKeyString} + +function compareKeyPChar(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + result := strcomp(pchar(key1), pchar(key2)); +end; {compareKeyPChar} + +function compareKeyDouble(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (double(key1^) < double(key2^)) then + result := -1 + else + if (double(key1^) > double(key2^)) then result := 1; +end; {compareKeyDouble} + +function compareKeySingle(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (single(key1^) < single(key2^)) then + result := -1 + else + if (single(key1^) > single(key2^)) then result := 1; +end; {compareKeySingle} + +function compareKeyInt32(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (int32(key1^) < int32(key2^)) then + result := -1 + else + if (int32(key1^) > int32(key2^)) then result := 1; +end; {compareKeyInt32} + +function compareKeyInt64(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + + if (int64(key1^) < int64(key2^)) then + result := -1 + else + if (int64(key1^) > int64(key2^)) then result := 1; + +end; // compareKeyInt64 + +(* + * CopyKey functions + * + *) + +procedure copyKeyPChar(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + strcopy(pchar(destKey), pchar(srcKey)); +end; {copyeKeyPChar} + +procedure copyKeyString(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + string(destKey^) := string(srcKey^); +end; {copyKeyString} + +procedure copyKeySingle(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + single(destKey^) := single(srcKey^); +end; {copyKeySingle} + +procedure copyKeyDouble(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + double(destKey^) := double(srcKey^); +end; {copyKeyDouble} + +procedure copyKeyInt32(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + int32(destKey^) := int32(srcKey^); +end; {copyKeyInt32} + +procedure copyKeyInt64(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + int64(destKey^) := int64(srcKey^); +end; {copyKeyInt64} + +(* + * KeySize functions + * + *) + +function keySizePChar(key:pointer):integer; +begin + result := 0; + if (key = NIL) then exit; + result := strlen(pchar(key))+1; +end; {keySizePChar} + +function keySizeString(key:pointer):integer; +begin + result := 0; + if (key = NIL) then exit; + result := length(string(key^))+1; +end; {keySizeString} + +function keySizeSingle(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(single); +end; {keySizeSingle} + +function keySizeDouble(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(double); +end; {keySizeDouble} + +function keySizeInt32(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(int32); +end; {keySizeInt32} + +function keySizeInt64(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(int64); +end; {keySizeInt64} + +(********************************) + +function compareUPtrs(u1, u2:uPtr):boolean; +begin + result := (u1.offset = u2.offset) +end; // compareUPtrs + +overload = = compareUPtrs; + +(* + * + * B^Tree object methods + * + *) + +constructor bTree.init; +var lResult:longint; +begin + memoryTree := fileName = ''; + treeName := filename; + // allocate the info section + // getmem(info, sizeof(BTreeInfo)); + new(info); + if (info = NIL) then runError; + fillchar(info^, sizeof(BTreeInfo), 0); + vfs := virtualFS; + with info^ do + begin + magic := B_NODE_TREEINFO; + setNull(root); + setNull(firstDeleted); + nodes := 0; + height := 0; + keys := 0; + bNodeSize := nodeSize; + treeType := tt; + end; {with} + + if not (memoryTree) then + begin + + if (vfs = NIL) then vfs := new(PStandardVFS, init); + if (vfs^.fExist(filename)) then + begin + if not (vfs^.fOpen(filename)) then runError; + if not vfs^.fRead(info^, sizeof(BTreeInfo)) then writeln('Error reading in bTreeInfo'); + end + else + begin + if not vfs^.fCreate(filename) then runError; + if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error writing bTreeInfo'); + end; +// if not vfs^.fClose() then runError; +// if not (vfs^.fOpen(filename, info^.bNodeSize)) then runError; + end; + + case info^.treeType of + BT_CUSTOM : InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + BT_PCHAR : InstallUserFunctions(compareKeyPChar, copyKeyPChar, keySizePChar); + BT_STRING : InstallUserFunctions(compareKeyString, copyKeyString, keySizeString); + BT_SINGLE : InstallUserFunctions(compareKeySingle, copyKeySingle, keySizeSingle); + BT_DOUBLE : InstallUserFunctions(compareKeyDouble, copyKeyDouble, keySizeDouble); + BT_INT32 : InstallUserFunctions(compareKeyInt32, copyKeyInt32, keySizeInt32); + BT_INT64 : InstalluserFunctions(compareKeyInt64, copyKeyInt64, keySizeInt64); + else + InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + end; // case + treeChanged := FALSE; // The superblock of the tree hasn't been modified +end; // bTree.init + +constructor bTree.open; +var + lResult:longint; +label safeExit; +begin + info := NIL; + memoryTree := FALSE; + treeChanged := FALSE; // The superblock of the tree hasn't been modified + treeName := filename; + InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + //if (filename = '') or (fsearch(filename, '') = '') then begin writeln(filename, ' does not exist'); exit; end; + + // allocate the info section + vfs := virtualFS; + + if (vfs = NIL) then vfs := new(PStandardVFS, init); + if not vfs^.fExist(filename) then + begin + writeln('in bTree.open(): file does not exist'); + goto safeExit; + end; + new(info); + fillchar(info^, sizeof(info^), 0); + if not vfs^.fOpen(filename) then runError; + if not vfs^.fRead(info^, sizeof(BTreeInfo)) then runError; + if (info^.magic <> B_NODE_TREEINFO) then runError; +// if not vfs^.fClose() then runError; +// vfs^.fOpen(filename, info^.bNodeSize); + + case info^.treeType of + BT_CUSTOM : InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + BT_PCHAR : InstallUserFunctions(compareKeyPChar, copyKeyPChar, keySizePChar); + BT_STRING : InstallUserFunctions(compareKeyString, copyKeyString, keySizeString); + BT_SINGLE : InstallUserFunctions(compareKeySingle, copyKeySingle, keySizeSingle); + BT_DOUBLE : InstallUserFunctions(compareKeyDouble, copyKeyDouble, keySizeDouble); + BT_INT32 : InstallUserFunctions(compareKeyInt32, copyKeyInt32, keySizeInt32); + BT_INT64 : InstalluserFunctions(compareKeyInt64, copyKeyInt64, keySizeInt64); + end; // case +safeExit: +end; // bTree.open + +function bTree.align; +begin + result := ((sizeof(uptr) + keyLength + 7) shr 3) shl 3; +end; {bTree.align} + +function bTree.allocEmptyNode; +var newNode:PbNode; +begin + saveSuperBlock(); + setNull(result); + + if (not isNull(info^.firstDeleted)) then + begin + newNode := loadPage(info^.firstDeleted); + if (newNode = NIL) then exit; + info^.firstDeleted := newNode^.parent; + end + else + begin + getmem(newNode, info^.bNodeSize); + fillchar(newNode^, info^.bNodeSize, 0); + inc(info^.nodes); + newNode^.tag := info^.nodes; + end; + + with newNode^ do + begin + magic := B_NODE_LEAF; + numKeys := 1; + size := dword(@newNode^.data) - dword(newNode); + setNull(left); + setNull(right); + setNull(parent); + setNull(reserved); + end; {with} + result := getAddress(newNode); + savePage(newNode); + discardPage(newNode); +// loadSuperBlock(); +end; {bTree.allocEmptyNode} + +function bTree.calcSize; +var + size:integer; + curNode:integer; + dataNode:PbNodeData; +begin + if (node = NIL) then exit; + dataNode := @node^.data; + size := dword(dataNode) - dword(node); + + for curNode := 1 to node^.numKeys do + begin + inc(size, align(keySize(@dataNode^.key))); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + if (node^.magic = B_NODE_INDEX) then inc(size, sizeof(uPtr)); + result := size; +end; + +procedure bTree.deAllocNode; +begin + if (node = NIL) then exit; + + with node^ do + begin + magic := B_NODE_OPEN; + setNull(left); + setNull(right); + setNull(reserved); + if not memoryTree then parent := info^.firstDeleted; + + size := dword(@node^.data) - dword(node); + fillchar(data, info^.bNodeSize - size, 0); + end; + if not memoryTree then info^.firstDeleted := getAddress(node); + savePage(node); + freemem(node, info^.bNodeSize); +end; {btree.deAllocNode} + +function bTree.deleteEntry; +var + bnode, tmpNode, neighbor, parent : PbNode; + neighborSize:integer; + + dataNode, nextDataNode, tmpDataNode:PbNodeData; + src, dst : pointer; + tmpLink:uPtr; + nodeSize:uInt32; + curNode, res, size: integer; + tmpTag:uInt32; + found : boolean; + + procedure getNeighbor; + var + l, r:PbNode; + begin + neighbor := NIL; + neighborSize := 0; + l := loadPage(bnode^.left); + r := loadPage(bnode^.right); + + if (l = NIL) then + begin + // this next check may be unecessary + if (r^.parent = bnode^.parent) then + begin + neighbor := r; + neighborSize := r^.size; + end; + exit + end; + + if (r = NIL) then + begin + // this next check may be unecessary + if (l^.parent = bnode^.parent) then + begin + neighbor := l; + neighborSize := l^.size; + end; + exit + end; + + if (l^.parent = r^.parent) then // left/right neighbors are children of the same parent + begin + if (l^.size < r^.size) then + begin + neighbor := l; + neighborSize := l^.size; + discardPage(r); + exit + end + else + begin + neighbor := r; + neighborSize := r^.size; + discardPage(l); + exit + end; + end + else // one of the neighbors has a different parent + begin + if (l^.parent = bnode^.parent) then + begin + neighbor := l; + neighborSize := l^.size; + discardPage(r); + exit + end + else + begin + neighbor := r; + neighborSize := r^.size; + discardPage(l); + exit + end; + end; + discardPage(l); + discardPage(r); + end; {getNeighbor local function} + +begin + + setNull(result); + + if (isNull(u)) then exit; + bnode := loadPage(u); + + if (bnode^.numKeys = 0) then runError; {this should never happen} + + if (bnode^.magic = B_NODE_LEAF) then + begin + + { case info^.treeType of + BT_STRING:writeln(f, 'Deleting ', string(key^), ' size: ', align(keySize(key))); + BT_PCHAR:writeln(f, 'Deleting ', pchar(key)); + end;} + + dataNode := @bnode^.data; + curNode := 0; + repeat + res := compareKeys(@dataNode^.key, key); + if (res >= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until (curNode = bnode^.numKeys); + + if (res <> 0) then + begin +// writeln(f, 'Could not find in leaf node'); + discardPage(bnode); + exit + end; + + // Since we found the entry, we will have to save the + // superblock at the end. Set treeChanged to TRUE + treeChanged := TRUE; + result := dataNode^.link; + + nodeSize := align(keySize(@dataNode^.key)); + + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); + + move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode))); + dec(bnode^.numKeys); + dec(bnode^.size, nodeSize); + dataNode := pointer(dword(bnode) + bnode^.size); + fillchar(dataNode^, nodeSize, 0); + dec(info^.keys); + + // check for merges and other stuff + if (getAddress(bnode) = info^.root) then + begin + if (bnode^.numKeys = 0) then + begin + setNull(info^.root); + dec(info^.height); + deAllocNode(bnode); + exit; + end; + end + else // not root + begin + if (bnode^.size < (info^.bNodeSize / 2)) then + begin + getNeighbor; + if (neighbor <> NIL) and (neighborSize <> 0) then + begin + if (bnode^.size + neighborSize <= info^.bNodeSize) then + begin +//writeln(f, 'Merging leaf pages'); + // if neighbor is a predecessor of bnode then swap them + if (getAddress(neighbor) = bnode^.left) then + begin + tmpNode := bnode; + bnode := neighbor; + neighbor := tmpNode; + end; + // merge the two nodes together + // bnode is the left node, neighbor is the right node + src := @neighbor^.data; + dst := pointer(dword(bnode) + bnode^.size); + + size := neighbor^.size - (dword(src) - dword(neighbor)); + + move(src^, dst^, size); + inc(bnode^.size, size); + inc(bnode^.numKeys, neighbor^.numKeys); + setRight(bnode^.left, getAddress(neighbor)); + bnode^.right := neighbor^.right; + tmpTag := neighbor^.tag; // save tag info + move(bnode^, neighbor^, info^.bNodeSize); + neighbor^.tag := tmpTag; // restore tag info + savePage(neighbor); + deleteEntry(bnode^.parent, NIL, getAddress(bnode)); + discardPage(neighbor); + deAllocNode(bnode); + exit; + end //if (bnode^.size + neighborSize <= info^.bNodeSize) + else // "acquire" an entry from a neighbor + begin + + if (getAddress(neighbor) = bnode^.right) then + begin +//writeln(f, 'acquiring from leaf bnode/neighbor'); + // neighbor node is to the right. Append an entry from it to bnode + dataNode := @neighbor^.data; + src := dataNode; + dst := pointer(dword(bnode) + bnode^.size); + nodeSize := align(keySize(@dataNode^.key)); + move(src^, dst^, nodeSize); + inc(bnode^.size, nodeSize); + inc(bnode^.numKeys); + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); + move(nextDataNode^, + dataNode^, + neighbor^.size - (dword(dataNode) - dword(neighbor)) - nodeSize); + dec(neighbor^.size, nodeSize); + dec(neighbor^.numKeys); + + // clear off the end of the neighbor node + dataNode := pointer(dword(neighbor) + neighbor^.size); + fillchar(dataNode^, nodeSize, 0); + savePage(bnode); + savePage(neighbor); + dataNode := dst; + replaceEntry(bnode^.parent, @dataNode^.key, getAddress(bnode)); + end + else + begin +//writeln(f, 'acquiring from leaf neighbor/bnode'); + // neighbor node is to the left. Take the last entry and prepend to bnode + dataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + // dataNode (hopefully) now points to the last key/value pair in neighbor + nodeSize := align(keySize(@dataNode^.key)); + src := @bnode^.data; + dst := src; + inc(dst, nodeSize); + move(src^, dst^, bnode^.size - (dword(src) - dword(bnode))); + dst := src; + src := dataNode; + move(src^, dst^, nodeSize); + fillchar(src^, nodeSize, 0); + dec(neighbor^.size, nodeSize); + dec(neighbor^.numKeys); + inc(bnode^.size, nodeSize); + inc(bnode^.numKeys); + savePage(bnode); + savePage(neighbor); + // this can be optimized by storing the previous + // node from the for loop above + dataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + replaceEntry(neighbor^.parent, @dataNode^.key, getAddress(neighbor)); + end; // else + discardPage(bnode); + discardPage(neighbor); + exit; + + end; + + end; //if (neighbor <> NIL) and (neighborSize <> 0) + discardPage(neighbor); + end; // if (bnode^.size < (info^.bNodeSize / 2)) + end; {else not root} + + end + else {not a leaf} + begin + dataNode := @bnode^.data; + nextDataNode := dataNode; + nodeSize := align(keySize(@dataNode^.key)); + inc(nextDataNode, nodeSize); + found := FALSE; + for curNode := 1 to bnode^.numKeys do + begin + found := (dataNode^.link = value); + if (found) then break; + if (curNode <> bnode^.numKeys) then + begin + dataNode := nextDataNode; + nodeSize := align(keySize(@nextDataNode^.key)); + inc(nextDataNode, nodeSize); + end; + end; + + if (not found) then + begin + if (nextDataNode^.link = value) then + nextDataNode^.link := dataNode^.link + else + begin + // writeln(f, 'Could not find in index node'); + discardPage(bnode); + exit; + end; + end; + {if (dataNode = nextDataNode) then inc(nextDataNode, align(keySize(@dataNode^.key)));} + // Copy this link into the next link so we can just delete this node + {nextDataNode^.link := dataNode^.link;} + + fillchar(dataNode^, nodeSize, 0); + move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode))); + + dec(bnode^.numKeys); + dec(bnode^.size, nodeSize); + dst := pointer(dword(bnode) + bnode^.size); + fillchar(dst^, nodeSize, 0); + // check for merges and other stuff + if (getAddress(bnode) = getRoot()) then + begin + if (bnode^.numKeys = 0) then + begin + dataNode := @bnode^.data; + info^.root := dataNode^.link; + dec(info^.height); + deAllocNode(bnode); + bnode := loadPage(info^.root); + setNull(bnode^.parent); + savePage(bnode); + discardPage(bnode); + exit + end; + end + else {not root} + begin + if (bnode^.size < (info^.bNodeSize / 2)) then + begin + getNeighbor; + if (neighbor<>NIL) and (neighborSize<>0) then + begin + if (bnode^.size + neighborSize <= info^.bNodeSize) then + begin +//writeln(f, 'merging index pages'); + // if neighbor is a predecessor of bnode then swap them + if (getAddress(neighbor) = bnode^.left) then + begin + tmpNode := bnode; + bnode := neighbor; + neighbor := tmpNode; + end; + // We need to get the key that currently resides between + // the bnode (left) link and the neighbor (right) link in the parent + parent := loadPage(bnode^.parent); + if (parent = NIL) then runError; // this would be bad + + dataNode := @parent^.data; + for curNode := 0 to parent^.numKeys-1 do + begin + if (dataNode^.link = getAddress(bnode)) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + // theoretically dataNode^.key is the key between the two links + + size := align(keySize(@dataNode^.key)); + + if ((bnode^.size + neighbor^.size + size - (dword(@bnode^.data) - dword(bnode))) > info^.bNodeSize) then + begin + // The key wouldn't fit. bail + discardPage(parent); + savePage(neighbor); + savePage(bnode); + discardPage(neighbor); + discardPage(bnode); + exit; + end; + + // copy the key to the end of bnode + src := @dataNode^.key; + dst := pointer(dword(bnode) + bnode^.size); + move(src^, dst^, keySize(@dataNode^.key)); + inc(bnode^.numKeys); // we just added a key + + dec(size, sizeof(uPtr)); + inc(bnode^.size, size); // increment by aligned size + inc(dst, size); // also increment the dst by aligned size + + discardPage(parent); // don't need the parent any more + // merge the two nodes together + // bnode is the left node, neighbor is the right node + src := @neighbor^.data; + // dst := pointer(dword(bnode) + bnode^.size); // set above + size := neighbor^.size - (dword(src) - dword(neighbor)); + move(src^, dst^, size); + inc(bnode^.size, size); + inc(bnode^.numKeys, neighbor^.numKeys); + + dataNode := @bnode^.data; + for curNode := 0 to bnode^.numKeys do + begin + setParent(dataNode^.link, getAddress(neighbor)); + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + + setRight(bnode^.left, getAddress(neighbor)); + bnode^.right := neighbor^.right; + tmpTag := neighbor^.tag; // save tag info + move(bnode^, neighbor^, info^.bNodeSize); + neighbor^.tag := tmpTag; // restore tag info + savePage(neighbor); + deleteEntry(bnode^.parent, NIL, getAddress(bnode)); + deAllocNode(bnode); + discardPage(neighbor); + + exit; + + end + else // "acquire" an entry from a neighbor + begin + // we need to do one extra check + if (getAddress(neighbor) = bnode^.right) then + begin + tmpLink := getAddress(bnode); + tmpDataNode := @neighbor^.data; + end + else + begin + tmpLink := getAddress(neighbor); + tmpDataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@tmpDataNode^.key))); + end; + + if (neighbor^.size - align(keySize(@tmpDataNode^.key)) < info^.bNodeSize / 2) then + begin + savePage(bnode); + discardPage(bnode); + discardPage(neighbor); + exit + end; + + // get the key between the neighbor/bnode pair + + parent := loadPage(bnode^.parent); + if (parent = NIL) then runError; // this would be bad + + dataNode := @parent^.data; + for curNode := 1 to parent^.numKeys do + begin + if (dataNode^.link = tmpLink) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + // theoretically dataNode^.key is the key between the two links + + nodeSize := align(keySize(@dataNode^.key)); + + if (getAddress(neighbor) = bnode^.right) then + begin +//writeln(f, 'acquiring from index bnode/neighbor'); + savePage(neighbor); // hmm.. did we change neighbor? + + src := @dataNode^.key; + dataNode := @neighbor^.data; + savePage(bnode); + insertEntry(getAddress(bnode), src, dataNode^.link); + replaceEntry(getAddress(parent), @dataNode^.key, getAddress(bnode)); + deleteEntry(getAddress(neighbor), NIL, dataNode^.link); + + end + else // neighbor is to the left, bnode is to the right + begin +//writeln(f, 'acquiring from index neighbor/bnode'); + + // We need to take the key from the parent node, coupled with the + // last value in the neighbor, and insert them into bnode + // To do this we have to move everything in bnode over + src := @dataNode^.key; + nodeSize := align(keySize(@dataNode^.key)); + dataNode := @bnode^.data; + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); + move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bnode))); + + // copy in the key + fillchar(dataNode^, nodeSize, 0); + move(src^, dataNode^.key, keySize(src)); + // now we update the size and keys + inc(bnode^.size, nodeSize); + inc(bnode^.numKeys); + // we still have to insert the value and update the child's parent ptr + tmpDataNode := dataNode; + + dataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + nextDataNode := dataNode; + inc(nextDataNode, align(keySize(@dataNode^.key))); + // insert the value + tmpDataNode^.link := nextDataNode^.link; + setParent(tmpDataNode^.link, getAddress(bnode)); + // need to change the insert (don't forget to update the child's parent ptr) + // insertEntry(getAddress(bnode), src, nextDataNode^.link); + savePage(bnode); + savePage(neighbor); + replaceEntry(getAddress(parent), @dataNode^.key, getAddress(neighbor)); + deleteEntry(getAddress(neighbor), NIL, nextDataNode^.link); + end; + discardPage(bnode); + discardPage(neighbor); + discardPage(parent); + + exit; + end; + discardPage(neighbor); + end; // if (bnode^.size + neighborSize <= info^.bNodeSize) then + + end; // if (bnode^.size < (info^.bNodeSize / 2)) then + + end; // else not root + + end; // not a leaf + + savePage(bnode); + discardPage(bnode); +end; {bTree.deleteEntry} + +procedure bTree.discardPage; +begin + // disk based trees allocate temporary memory to hold a page. So, if we're not + // a memory tree, dispose of the temp page + if not (memoryTree) then if (node <> NIL) then freemem(node, info^.bNodeSize); +end; {bTree.discardPage} + +function bTree.getAddress; +var u:uPtr; +begin + setNull(u); + + if (memoryTree) then + u.bPtr := node + else + u.offset := node^.tag; { * bNodeSize;} + + result := u; +end; {bTree.getAddress} + +function bTree.getRoot; +begin + if (info = NIL) then + setNull(result) + else + result := info^.root; +end; // bTree.getRoot + +function bTree.insertEntry; +var + tmpUPtr, tmpRoot:uPtr; + dataNode, nextDataNode, prevDataNode:PbNodeData; + midKey:BTreeSearchRec; + keyLength, curNode, dataSize, res:integer; + bnode, newNode, rootNode:PbNode; + nodeSize:uInt32; +begin + result := FALSE; + if (key = NIL) then exit; + +{ case info^.treeType of + BT_STRING:writeln(f, 'Inserting ', string(key^)); + BT_PCHAR:writeln(f, 'Inserting ', pchar(key)); + end;} + + midKey.key := NIL; + midKey.keySize := 0; + + keyLength := keySize(key); + + if (keyLength = 0) or (keyLength > info^.bNodeSize) then exit; + + // The superblock will have to be updated when the tree is destructed + // set treeChanged to TRUE + treeChanged := TRUE; + + nodeSize := align(keyLength); + + if (isNull(info^.root)) then + begin + + (* + * This case should only occur once when a directory is first created. + *) + tmpRoot := allocEmptyNode(); +// info^.root := allocEmptyNode(); + if (isNull(tmpRoot)) then runError; + + rootNode := loadPage(tmpRoot); + info^.root := tmpRoot; + inc(info^.keys); + inc(info^.height); + rootNode^.magic := B_NODE_LEAF; + dataNode := @rootNode^.data; + + fillchar(dataNode^, nodeSize, 0); + + dataNode^.link := value; + copyKey(key, @dataNode^.key); + + inc(rootNode^.size, nodeSize); {hm.. should I add the sizeof the link?} + + savePage(rootNode); + discardPage(rootNode); + result := TRUE; + exit + end; + + bnode := loadPage(u); + + if (bnode^.size + nodeSize <= info^.bNodeSize) then + begin + + dataNode := @bnode^.data; + curNode := 0; + repeat + res := compareKeys(key, @dataNode^.key); + if (res <= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until curNode = bnode^.numKeys; + + if (res = 0) then + begin + dataNode^.link := value; + savePage(bnode); + discardPage(bnode); + result := TRUE; + exit; + end; + + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); {cast to byte size records} + + move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bNode))); + + fillchar(dataNode^, nodeSize, 0); + copyKey(key, @dataNode^.key); + + dataNode^.link := value; + + if (bnode^.magic = B_NODE_INDEX) then + begin + if (not isNull(dataNode^.link)) then setParent(dataNode^.link, getAddress(bnode)); + { if (curNode = bnode^.numKeys) then + begin} + tmpUPtr := dataNode^.link; + dataNode^.link := nextDataNode^.link; + nextDataNode^.link := tmpUPtr; + { end;} + end; + inc(bnode^.numKeys); + + if (bnode^.magic = B_NODE_LEAF) then inc(info^.keys); + inc(bNode^.size, nodeSize); + savePage(bnode); + end + else + begin + + newNode := loadPage(allocEmptyNode); + if (newNode = NIL) then runError; + + newNode^.magic := bnode^.magic; + + newNode^.left := getAddress(bnode); + newNode^.right := bnode^.right; + newNode^.parent := bnode^.parent; + + if not(isNull(newNode^.right)) then setLeft(newNode^.right, getAddress(newNode)); + bnode^.right := getAddress(newNode); + + dataNode := @bnode^.data; + + {for curNode:=0 to (bnode^.numKeys div 2)-1 do + inc(dataNode, align(keySize(@dataNode^.key)));} + curNode := 1; + repeat + prevDataNode := dataNode; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until (dword(dataNode) - dword(bnode) >= bnode^.size shr 1); + + {midKey := dataNode;} + dataNode := prevDataNode; + midKey.keySize := keySize(@dataNode^.key); + GetMem(midKey.key, midKey.KeySize); + {if (midKey.key = NIL) then halt;} + copyKey(@dataNode^.key, midKey.key); + + dec(curNode); + + if (bnode^.magic = B_NODE_LEAF) then + begin + + inc(dataNode, align(keySize(@dataNode^.key))); + + dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); + move(dataNode^, newNode^.data, dataSize); + fillchar(dataNode^, dataSize, 0); + + dec(bnode^.size, dataSize); + newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); + + newNode^.numKeys := bnode^.numKeys - curNode; + + dec(bnode^.numKeys, newNode^.numKeys); + + savePage(bnode); + savePage(newNode); + + if (compareKeys(key, midKey.key) <= 0) then + insertEntry(getAddress(bnode), key, value) + else + insertEntry(getAddress(newNode), key, value); + + end + else // not a leaf + begin + + inc(dataNode, align(keySize(@dataNode^.key))); + + dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); + move(dataNode^, newNode^.data, dataSize); + + fillchar(dataNode^, dataSize, 0); + + dec(bnode^.size, dataSize); + // we need to adjust the size because we're taking out the midkey + // and we're also adding that extra pointer + dec(bnode^.size, align(keySize(midkey.key))); + inc(bnode^.size, sizeof(uPtr)); + + newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); + + newNode^.numKeys := bnode^.numKeys - curNode; + dec(bnode^.numKeys, newNode^.numKeys); + dec(bnode^.numKeys); + + savePage(bnode); + savePage(newNode); + + dataNode := @newNode^.data; + for curNode := 0 to newNode^.numKeys do + begin + setParent(dataNode^.link, getAddress(newNode)); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + //savePage(bnode); + + if (compareKeys(key, midKey.key) <= 0) then + insertEntry(getAddress(bnode), key, value) + else + insertEntry(getAddress(newNode), key, value); + + end; + + (* + * If we were the root node, then create a new one + *) + + if (getAddress(bnode) = info^.root) then + begin + + tmpRoot := allocEmptyNode(); +// info^.root := allocEmptyNode; + rootNode := loadPage(tmpRoot); + info^.root := tmpRoot; + setParent(getAddress(bnode), info^.root); + setParent(getAddress(newNode), info^.root); + + inc(info^.height); + {dataNode := @newNode^.data; } + dataNode := @rootNode^.data; + with rootNode^ do + begin + magic := B_NODE_INDEX; + {numKeys := 1;} + dataNode^.link := getAddress(bnode); + + keyLength := midKey.keySize; + copyKey(midKey.key, @dataNode^.key); + inc(size, align(keyLength)); + inc(size, sizeof(uPtr)); + inc(dataNode, align(keyLength)); + dataNode^.link := getAddress(newNode); + + end; {with} + savePage(rootNode); + discardPage(rootNode); + end {if oldNode = root} + else + begin + insertEntry(bnode^.parent, midKey.key, getAddress(newNode)); + end; + if (midKey.key <> NIL) and (midKey.keySize <> 0) then FreeMem(midKey.key, midKey.keySize); + discardPage(newNode); + end; {if out of space in this node} + discardPage(bnode); + result := TRUE +end; {bTree.insertEntry} + +function bTree.isNull; +begin + {if (memoryTree) then + result := (u.bPtr = NIL) + else} + result := (u.offset = 0) +end; {bTree.isNull} + +function bTree.loadPage; +var + node:PbNode; +begin + result := NIL; + if isNull(u) then exit; + + if (memoryTree) then + node := u.bPtr + else + begin + if not vfs^.fSeek(dword(u.offset) * info^.bNodeSize) then runError; {exit} + getmem(node, info^.bNodeSize); + if (node = NIL) then exit; + if not vfs^.fRead(node^, info^.bNodeSize) then runError; {exit;} + end; // !memoryTree + + result := node +end; // bTree.loadPage + +procedure bTree.loadSuperBlock; +begin + if memoryTree then exit; + if not vfs^.fSeek(0) then writeln('Error seeking in bTree.loadSuperBlock'); + if not vfs^.fRead(info^, sizeof(BTreeInfo)) then writeln('Error reading in bTree Info in bTree.loadSuperBlock'); +end; + +function bTree.replaceEntry; +var + dataNode:PbNodeData; + node:PbNode; + curNode, res, srcKeySize, dstKeySize, deltaSize:integer; + src, dst:pointer; +begin + result := FALSE; + if ((key = NIL) or (isNull(u))) then exit; + node := loadPage(u); + if (node = NIL) then exit; + + if (node^.magic <> B_NODE_INDEX) then + begin + writeln('Error! Not in index nodes.'); + discardPage(node); + exit + end; + + dataNode := @node^.data; + // possible bug on count + for curNode := 0 to node^.numKeys do + begin + if (dataNode^.link = value) then + begin + srcKeySize := keySize(key); + dstKeySize := keySize(@dataNode^.key); + deltaSize := align(srcKeySize) - align(dstKeySize); + if (deltaSize = 0) then + begin + // source and dest keys are equal length. Replace old key with new key + fillchar(dataNode^.key, dstKeySize, 0); + move(key^, dataNode^.key, srcKeySize); + savePage(node); + end + else + if (deltaSize < 0) then + begin + // source key is smaller than dest key. + fillchar(dataNode^.key, dstKeySize, 0); + move(key^, dataNode^.key, srcKeySize); + src := dataNode; + dst := dataNode; + inc(src, align(dstKeySize)); // no, this isn't backwards + inc(dst, align(srcKeySize)); // we're copying from the old size + move(src^, dst^, node^.size - (dword(src) - dword(node))); + inc(node^.size, deltaSize); // deltaSize is negative + savePage(node); + end + else + begin + // source key is bigger than dest key + if ((deltaSize + node^.size) > info^.bNodeSize) then + begin +// writeln(f, 'Source key would cause split'); + end + else // source key will fit + begin + // dataNode points to the value/key pair + src := dataNode; + dst := dataNode; + inc(src, align(dstKeySize)); + inc(dst, align(srcKeySize)); + move(src^, dst^, node^.size - (dword(src) - dword(node))); + fillchar(dataNode^.key, dstKeySize, 0); + move(key^, dataNode^.key, srcKeySize); + inc(node^.size, deltaSize); + savePage(node); + end; + + end; + + break; + end; + inc(dataNode, align(keySize(@dataNode^.key))); + end; + discardPage(node); + result := TRUE; +end; {bTree.replaceEntry} + +procedure bTree.savePage; +begin + if (memoryTree) then exit; + + if (node = NIL) then exit; + + if not vfs^.fSeek(dword(node^.tag) * info^.bNodeSize) then writeln('Error seeking in bTree.savePage()'); + if not vfs^.fWrite(node^, info^.bNodeSize) then writeln('Error writing page in bTree.savePage()'); +end; // bTree.savePage + +procedure bTree.saveSuperBlock; +begin + if (not(memoryTree)) then + begin +// if not vfs^.fClose() then writeln('Error calling fClose (for the first time) file in bTree.done()'); +// if not vfs^.fOpen(treeName, 1) then writeln('Error calling fOpen in bTree.done()'); + if not vfs^.fSeek(0) then writeln('Error seeking in bTree.saveSuperBlock()'); // reset back to the beginning + if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error saving superblock in bTree.saveSuperBlock()'); + // disposing the vfs pointer will close the file as necessary + end; + +end; // bTree.saveSuperBlock + +procedure bTree.setLeft; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.left := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setLeft} + +procedure bTree.setParent; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.parent := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setParent} + +procedure bTree.setNull; +begin +{ if (memoryTree) then} + u.offset := 0 + {else + u.offset := -1;} +end; {bTree.setNull} + +procedure bTree.setRight; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.right := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setRight} + +function bTree.fetchFirstKey; +var + node:PbNode; + dataNode:PbNodeData; +begin + result := FALSE; + if isNull(u) then exit; + node := loadPage(u); + if (node = NIL) then exit; + + dataNode := @node^.data; + if (node^.magic = B_NODE_LEAF) then + begin + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + if (searchRec.key = NIL) then exit; + + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + + result := TRUE; + end + else + begin + result := fetchFirstKey(dataNode^.link, searchRec); + end; + discardPage(node); +end; {bTree.fetchFirstKey} + +function bTree.fetchLastKey; +var + node:PbNode; + dataNode:PbNodeData; + curNode:integer; +begin + result := FALSE; + if isNull(u) then exit; + node := loadPage(u); + if (node = NIL) then exit; + + dataNode := @node^.data; + + if (node^.magic = B_NODE_LEAF) then + begin + for curNode := 1 to node^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + if (searchRec.key = NIL) then exit; + + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + result := TRUE; + end + else + begin + for curNode := 1 to node^.numKeys do + inc(dataNode, align(keySize(@dataNode^.key))); + result := fetchLastKey(dataNode^.link, searchRec); + end; + discardPage(node); +end; {bTree.fetchLastKey} + +procedure bTree.findLeafNode; +var + bNode:PbNode; + dataNode:PbNodeData; + i:integer; + link:uPtr; +begin + setNull(result); + if (key = NIL) then exit; + + if (isNull(u)) then exit; + + bnode := loadPage(u); + + if (bnode^.magic = B_NODE_LEAF) then + begin + result := u; + discardPage(bnode); + exit; + end + else + begin + dataNode := @bnode^.data; + for i:=0 to bnode^.numKeys -1 do + begin + if (compareKeys(key, @dataNode^.key) <= 0) then + begin + link := dataNode^.link; + discardPage(bnode); + result := findLeafNode(link, key); + exit; + end; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + link := dataNode^.link; + discardPage(bnode); + result := findLeafNode(link, key); + end; +end; {bTree.findLeafNode} + +procedure bTree.freeAll; +var node:PbNode; +begin + if not(memoryTree) then exit; + freePage(getRoot()); + setNull(info^.root); +end; {bTree.freeAll} + +procedure bTree.freePage; +var + curNode:integer; + dataNode:PbNodeData; + node : PbNode; +begin + + if isNull(u) then exit; + + node := u.bPtr; + curNode := 0; + if (node^.magic = B_NODE_INDEX) then + begin + dataNode := @node^.data; + for curNode := 0 to node^.numKeys-1 do + begin + freePage(dataNode^.link); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + freePage(dataNode^.link); + end; + freemem(node, info^.bNodeSize); +end; // bTree.freePage + +function bTree.Delete; +begin + result := deleteEntry(findLeafNode(getRoot(), key), key, value); +end; {bTree.Delete} + +function bTree.Find; +var + bnode:PbNode; + dataNode:PbNodeData; + i, res:integer; + +begin + result := FALSE; + setNull(u); + + bnode := loadPage(findLeafNode(getRoot(), key)); + if (bnode = NIL) then exit; {oops! serious problem} + + if (bnode^.magic <> B_NODE_LEAF) then writeln('Error! not in leaf nodes'); + + dataNode := @bnode^.data; + + for i:=0 to bnode^.numKeys-1 do + begin + res := compareKeys(@dataNode^.key, key); // strcomp(@dataNode^.key.str[1], pchar(key)); + if (res >= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for i} + + // did we find it? + if (res = 0) then + begin + u := dataNode^.link; + result := TRUE; + end; {if res = 0} + + discardPage(bnode); +end; {bTree.Find} + +procedure bTree.FindClose; +begin + with searchRec do + begin + setNull(value); + if (key<>NIL) then + FreeMem(key, align(keySize)); + key := NIL; + keySize := 0; + end; {with F} +end; {bTree.FindClose} + +function bTree.FindFirst; +var + node:PbNode; + dataNode:PbNodeData; + curNode, res:integer; +begin + result := FALSE; + + with searchRec do + begin + setNull(value); + key := NIL; + keySize := 0 + end; {with searchRec} + if (key = NIL) then exit; + node := loadPage(findLeafNode(getRoot(), key)); + if (node = NIL) then exit; + + dataNode := @node^.data; + curNode := 0; + repeat + res := compareKeys(key, @dataNode^.key); + if (res <= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until curNode = node^.numKeys; + + if (res = 0) then // we found it + begin + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + fillchar(searchRec.key^, align(searchRec.keySize), 0); + if (searchRec.key = NIL) then exit; + copyKey(@dataNode^.key, searchRec.key); + result := TRUE; + end; {if res = 0} + + discardPage(node); +end; {bTree.FindFirst} + +function bTree.FindNext; +var + nextNode:uPtr; + node:PbNode; + dataNode:PbNodeData; + curNode, res:integer; + +begin + result := FALSE; + + if (searchRec.key = NIL) then exit; + + node := loadPage(findLeafNode(getRoot(), searchRec.key)); + if (node = NIL) then + begin + findClose(searchRec); + exit + end; + + dataNode := @node^.data; + curNode := 0; + repeat + res := compareKeys(@dataNode^.key, searchRec.key); + if (res > 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until curNode = node^.numKeys; + + if (res <= 0) and (curNode = node^.numKeys) then + begin + nextNode := node^.right; + discardPage(node); + node := loadPage(nextNode); + if (node = NIL) then + begin + findClose(searchRec); + exit + end; {node = NIL} + dataNode := @node^.data; + end; + + FreeMem(searchRec.key, align(searchRec.keySize)); + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + searchRec.key := NIL; + GetMem(searchRec.key, align(searchRec.keySize)); + // sanity check + if (searchRec.key <> NIL) then + begin + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + result := TRUE + end; + + discardPage(node); +end; {bTree.FindNext} + +function bTree.FindPrev; +var + prevNode:uPtr; + node:PbNode; + lastDataNode, dataNode:PbNodeData; + curNode, res:integer; + +begin + result := FALSE; + if (searchRec.key = NIL) then exit; + + node := loadPage(findLeafNode(getRoot(), searchRec.key)); + if (node = NIL) then + begin + findClose(searchRec); + exit + end; + + dataNode := @node^.data; + lastDataNode := dataNode; + curNode := 0; + res := compareKeys(searchRec.key, @dataNode^.key); + + if (res > 0) then + repeat + lastDataNode := dataNode; + inc(curNode); + inc(dataNode, align(keySize(@dataNode^.key))); + res := compareKeys(searchRec.key, @dataNode^.key); + until (res <= 0) or (curNode = node^.numKeys); + + dataNode := lastDataNode; + + if ((res <= 0) and (curNode = 0)) then + begin + prevNode := node^.left; + discardPage(node); + node := loadPage(prevNode); + if (node = NIL) then + begin + FindClose(searchRec); + exit + end; + dataNode := @node^.data; + // scan to the end of the previous node. + // If there's only 1 key, this will do nothing + for curNode := 1 to node^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + end; + + FreeMem(searchRec.key, align(searchRec.keySize)); + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + searchRec.key := NIL; + GetMem(searchRec.key, align(searchRec.keySize)); + // sanity check + if (searchRec.key <> NIL) then + begin + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + result := TRUE + end; + + discardPage(node) +end; {bTree.FindPrev} + +function bTree.GetFirstKey; +begin + with searchRec do + begin + setNull(value); + key := NIL; + keySize := 0; + end; {with searchRec} + result := fetchFirstkey(getRoot(), searchRec); + +end; {bTree.GetFirstKey} + +function bTree.GetKeyCount; +begin + if (info <> NIL) then + result := info^.keys + else + result := 0 +end; // bTree.GetKeyCount + +function bTree.GetLastKey; +begin + with searchRec do + begin + setNull(value); + key := NIL; + keySize := 0; + end; {with searchRec} + result := fetchLastKey(getRoot(), searchRec); +end; {bTree.GetLastKey} + +procedure bTree.Print; +var + curNode:integer; + dataNode:PbNodeData; + node : PbNode; + link : uPtr; +begin + if isNull(u) then exit; + PrintInfo(f, u); + node := loadPage(u); + curNode := 0; + if (node^.magic = B_NODE_INDEX) then + begin + dataNode := @node^.data; + for curNode := 0 to node^.numKeys-1 do + begin + Print(f, dataNode^.link); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + Print(f, dataNode^.link); + end; + discardPage(node); +end; {bTree.Print} + +procedure bTree.PrintInfo; +const leafStr:array[boolean] of string[5] = ('INDEX', 'LEAF'); +var + dataNode:PbNodeData; + curNode:integer; + node : PbNode; + i:integer; +begin + node := loadPage(u); + if (node = NIL) then exit; + write(f, ' ', hex(dword(node^.parent.offset))); + writeln(f, ' ', leafStr[node^.magic = B_NODE_LEAF], ' (', hex(node^.magic), ')'); + write(f, hex(dword(node^.left.bPtr)), ' <-> ', hex(getAddress(node).u), ' <-> ', hex(dword(node^.right.bPtr))); + writeln(f, ' | capacity ', node^.size, '[', calcSize(node), '](', info^.bNodeSize, ') | keys:', node^.numKeys); + if (node^.numKeys = 0) then exit; + dataNode := @node^.data; + + curNode := 0; + for curNode := 0 to node^.numKeys-(ord(node^.magic = B_NODE_LEAF)) do + begin + + write(f, '| ', curNode:2, '| '); + + {if (node^.magic = B_NODE_INDEX) then} + write(f, '[', hex(dword(dataNode^.link.offset)), ']'); + + if (curNode <> node^.numKeys) then + case info^.treeType of + BT_custom:with TBlockRun(dataNode^.key) do writeln(f, ' {', AG, ':', start, ':', len, '} '); + BT_INT32:writeln(f, ' {', hex(dataNode^.key.i), '} '); + BT_INT64:writeln(f, ' {', hex(dataNode^.key.x), '} '); + BT_SINGLE:writeln(f, ' {', dataNode^.key.s, '} '); + BT_DOUBLE:writeln(f, ' {', dataNode^.key.d, '} '); + BT_STRING:writeln(f, ' {', dataNode^.key.str, '} '); + BT_PCHAR:begin + (*writeln(f, ' {', pchar(@dataNode^.key)^, '} '); *) + write(f, ' {'); + i:=0; + while (dataNode^.key.str[i]<>#0) do + begin + write(f, dataNode^.key.str[i]); + inc(i); + end; + writeln(f, '} '); + end; + end; + + inc(dataNode, align(keySize(@dataNode^.key))); + end; + discardPage(node); + writeln(f); +end; {bTree.PrintInfo} + +function bTree.Insert; +begin + result := insertEntry(findLeafNode(getRoot(), key), key, value); +end; {bTree.Insert} + +procedure bTree.InstallUserFunctions; +begin + compareKeys := cmpFunc; + copyKey := copyProc; + keySize := ksFunc; +end; {bTree.InstallUserFunc} + +procedure bTree.PrintList; +var + dataNode:PbNodeData; + node:PbNode; + link:uPtr; + i, j : integer; + f:text; +begin + + if (isNull(getRoot())) then exit; + assign(f, filename); + {$I-} rewrite(f); {$I+} + if (IOResult() <> 0) then exit; + + node := loadPage(getRoot()); + if (node = NIL) then exit; + while (node^.magic = B_NODE_INDEX) do + begin + dataNode := @node^.data; + link := dataNode^.link; + discardPage(node); + node := loadPage(link); + end; + + while (not isNull(link)) do + begin + dataNode := @node^.data; + for i := 0 to node^.numKeys -1 do + begin + case info^.treeType of + BT_CUSTOM:with TBlockRun(dataNode^.key) do writeln(f, AG, ':', start, ':', len); + BT_INT32:writeln(f, hex(dataNode^.key.i)); + // BT_INT64:writeln(f, dataNode^.key.x); + BT_SINGLE:writeln(f, dataNode^.key.s); + BT_DOUBLE:writeln(f, dataNode^.key.d); + BT_STRING:writeln(f, dataNode^.key.str); + BT_PCHAR:begin + j:=0; + while (dataNode^.key.str[j]<>#0) do + begin + write(f, dataNode^.key.str[j]); + inc(j); + end; + writeln(f); + end; + end; // case + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for i} + link := node^.right; + discardPage(node); + node := loadPage(link); + end; {while} + {$I-} close(f); {$I+} + if (IOResult() <> 0) then writeln('Error calling close() in bTree.PrintList'); +end; {bTree.PrintList} + +procedure bTree.PrintWholeTree; +var f:text; +begin + assign(f, filename); + {$I-} rewrite(f); {$I+} + if (IOResult() <> 0) then exit; + Print(f, getRoot()); + {$I-} close(f); {$I+} + if (IOResult() <> 0) then writeln('Error calling close() in bTree.PrintWholeTree'); +end; {bTree.PrintWholeTree} + +destructor bTree.done; +begin + if (not(memoryTree) and treeChanged) then + begin +// if not vfs^.fClose() then writeln('Error calling fClose (for the first time) file in bTree.done()'); +// if not vfs^.fOpen(treeName, 1) then writeln('Error calling fOpen in bTree.done()'); + if not vfs^.fSeek(0) then writeln('Error seeking in bTree.done()'); // reset back to the beginning + if not vfs^.fWrite(info^, sizeof(BTreeInfo)) then writeln('Error saving superblock in bTree.done()'); + if not vfs^.fClose() then writeln('Error calling fClose file in bTree.done()'); + // disposing the vfs pointer will close the file as necessary + end; + if (not isNull(getRoot())) then freeAll; + + { with info^ do + begin + writeln('tree height: ', height); + writeln('tree leaf key count: ', keys); + writeln('Total number of nodes: ', nodes); + // freeAll; + end; + } + + dispose(info); + if (vfs <> NIL) then dispose(vfs, done); + info := NIL; +end; // bTree.done + +constructor TQueue.init; +begin + queue := NIL; + elements := 0; + maxElements := elementCount; + size := elementSize; + head := 0; + tail := 0; + if (size <> 0) and (maxElements <> 0) then + begin + GetMem(queue, size*maxElements); + fillchar(queue^, size*maxElements, 0); + end; +end; // TQueue.init + +function TQueue.dequeue; +var p:pointer; +begin + result := FALSE; + if (queue = NIL) or (elements = 0) then exit; + p := queue; + inc(p, head*size); + head := (head+1) mod maxElements; + move(p^, item, size); + dec(elements); + result := TRUE; +end; // TQueue.dequeue + +function TQueue.enqueue; +var p:pointer; +begin + result := FALSE; + if (queue = NIL) or (elements = maxElements) then exit; + p := queue; + inc(p, tail*size); + move(item, p^, size); + tail := (tail+1) mod maxElements; + inc(elements); + result := TRUE; +end; // TQueue.enqueue + +function TQueue.getElementCount; result := elements; + +destructor TQueue.done; +begin + if (size <> 0) and (maxElements <> 0) then FreeMem(queue, size*maxElements); + queue := NIL; + elements := 0; + maxElements := 0; + size := 0; + head := 0; + tail := 0; +end; // TQueue.done + +end. \ No newline at end of file diff --git a/listsbak.pas b/listsbak.pas new file mode 100755 index 0000000..964bcc9 --- /dev/null +++ b/listsbak.pas @@ -0,0 +1,1897 @@ +{$TPO-} // treat all pointer inc/dec's as byte adjusts +unit lists; + +interface + +const B_NODE_LEAF = $4641454C; // LEAF +const B_NODE_INDEX = $58444E49; // INDX +const B_NODE_OPEN = $4E45504F; // OPEN + +type + int8 = shortInt; + int16 = smallInt; + int32 = longInt; + + uInt8 = byte; + uInt16 = word; + uInt32 = dWord; + +type + treeTypes = (BT_CUSTOM, BT_PCHAR, BT_STRING, BT_SINGLE, BT_DOUBLE, BT_INT32, BT_INT64); + PbNode = ^TbNode; + PbNodeData = ^TbNodeData; + + uPtr = packed record + case integer of + 0: (i : int32); + 1: (u : uInt32); + 2: (s : single); + 3: (d : double); + 4: (p : pointer); + 5: (bPtr : PbNode); + 6: (offset : int64); + { 7: (iAddr : inodeAddr);} + end; + + TbNode = packed record + magic : uInt32; + tag : uInt32; + numKeys : uInt32; + size : uInt32; + left : uPtr; + right : uPtr; + parent : uPtr; + reserved : uPtr; + data : array[0..0] of byte; + end; + + TbNodeData = packed record + link : uPtr; + key : record + case treeTypes of + BT_PCHAR, + BT_STRING: (str:string[1]); + BT_SINGLE: (s:single); + BT_DOUBLE: (d:double); + BT_INT32 : (i:int32); + BT_INT64 : (x:int64); + end; {record} + end; {TbNode} + +type + BTreeSearchRec = record + value : uPtr; + key : pointer; + keySize:integer; + end; {BTreeSearchRec} + +type + PBTreeInfo = ^BTreeInfo; + BTreeInfo = packed record + root : uPtr; + firstDeleted : uPtr; + nodes : uInt32; + height : uInt32; + keys : uInt32; + bNodeSize : uInt32; + treeType : treeTypes; + end; {BTreeHeader} + +type compareKeyFunc = function(key1, key2:pointer):integer; +type copyKeyProc = procedure(srcKey, destKey:pointer); +type keySizeFunc = function(key:pointer):integer; + +type + PbTree = ^bTree; + bTree = object + private + compareKeys : compareKeyFunc; + copyKey : copyKeyProc; + keySize : keySizeFunc; + info : PBTreeInfo; + dataFile : file; + memoryTree : boolean; + f:text; + + function align(keyLength:integer):integer; + function allocEmptyNode:uPtr; + function calcSize(node:PbNode):integer; + procedure deAllocNode(node:PbNode); + function deleteEntry(u:uPtr; key:pointer; value:uPtr):uPtr; + procedure discardPage(node:PbNode); + function fetchFirstKey(u:uPtr; var searchRec:BTreeSearchRec):boolean; + function fetchLastKey(u:uPtr; var searchRec:BTreeSearchRec):boolean; + function findLeafNode(u:uPtr; key:pointer):uPtr; + procedure freeAll; + procedure freePage(u:uPtr); + function getAddress(node:PbNode):uPtr; + function insertEntry(u:uPtr; key:pointer; value:uPtr):boolean; + function isNull(u:uPtr):boolean; + function loadPage(u:uPtr):PbNode; + function replaceEntry(u:uPtr; key:pointer; value:uPtr):boolean; + procedure savePage(node:PbNode); + procedure setLeft(u:uPtr; newPtr:uPtr); + procedure setNull(var u:uPtr); + procedure setRight(u:uPtr; newPtr:uPtr); + procedure setParent(u:uPtr; newPtr:uPtr); + + public + constructor init(const filename:string; nodeSize:uInt32; tt:treeTypes); + function Delete(key:pointer; value:uPtr):uPtr; + function Insert(key:pointer; value:uPtr):boolean; + procedure InstallUserFunctions(cmpFunc:compareKeyFunc; copyProc:copyKeyProc; ksFunc:keySizeFunc); + function Find(key:pointer; var u:uPtr):boolean; + procedure FindClose(var searchRec:BTreeSearchRec); + function FindFirst(key:pointer; var searchRec:BTreeSearchRec):boolean; + function FindNext(var searchRec:BTreeSearchRec):boolean; + function FindPrev(var searchRec:BTreeSearchRec):boolean; + function GetFirstKey(var searchRec:BTreeSearchRec):boolean; + function GetLastKey(var searchRec:BTreeSearchRec):boolean; + procedure PrintInfo(u:uPtr); + procedure Print(u:uPtr); + procedure PrintList; + procedure PrintWholeTree; + destructor done; + end; {bTree} + +implementation + +uses strings, dos; + +function compareKeyNull(key1, key2:pointer):integer; result := 0; {compareKeyNull} + +procedure copyKeyNull(srcKey, destKey:pointer); begin end; {compareKeyNull} + +function keySizeNull(key:pointer):integer; result := 0; {keySizeNull} + +(* + * CompareKey functions + * + *) + +function compareKeyString(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if string(key1^) < string(key2^) then + result := -1 + else + if string(key1^) > string(key2^) then result := 1; +end; {comapreKeyString} + +function compareKeyPChar(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + result := strcomp(pchar(key1), pchar(key2)); +end; {compareKeyPChar} + +function compareKeyDouble(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (double(key1^) < double(key2^)) then + result := -1 + else + if (double(key1^) > double(key2^)) then result := 1; +end; {compareKeyDouble} + +function compareKeySingle(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (single(key1^) < single(key2^)) then + result := -1 + else + if (single(key1^) > single(key2^)) then result := 1; +end; {compareKeySingle} + +function compareKeyInt32(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (int32(key1^) < int32(key2^)) then + result := -1 + else + if (int32(key1^) > int32(key2^)) then result := 1; +end; {compareKeyInt32} + +function compareKeyInt64(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + if (int64(key1^) < int64(key2^)) then + result := -1 + else + if (int64(key1^) > int64(key2^)) then result := 1; +end; {compareKeyInt64} + +(* + * CopyKey functions + * + *) + +procedure copyKeyPChar(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + strcopy(pchar(destKey), pchar(srcKey)); +end; {copyeKeyPChar} + +procedure copyKeyString(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + string(destKey^) := string(srcKey^); +end; {copyKeyString} + +procedure copyKeySingle(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + single(destKey^) := single(srcKey^); +end; {copyKeySingle} + +procedure copyKeyDouble(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + double(destKey^) := double(srcKey^); +end; {copyKeyDouble} + +procedure copyKeyInt32(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + int32(destKey^) := int32(srcKey^); +end; {copyKeyInt32} + +procedure copyKeyInt64(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + int64(destKey^) := int64(srcKey^); +end; {copyKeyInt64} + +(* + * KeySize functions + * + *) + +function keySizePChar(key:pointer):integer; +begin + result := 0; + if (key = NIL) then exit; + result := strlen(pchar(key))+1; +end; {keySizePChar} + +function keySizeString(key:pointer):integer; +begin + result := 0; + if (key = NIL) then exit; + result := length(string(key^))+1; +end; {keySizeString} + +function keySizeSingle(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(single); +end; {keySizeSingle} + +function keySizeDouble(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(double); +end; {keySizeDouble} + +function keySizeInt32(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(int32); +end; {keySizeInt32} + +function keySizeInt64(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(int64); +end; {keySizeInt64} + +(********************************) + +function compareUPtrs(u1, u2:uPtr):boolean; +begin + result := (u1.offset = u2.offset); +end; {compareUPtrs} + +overload = = compareUPtrs; + +(* + * + * B^Tree object methods + * + *) + +constructor bTree.init; +var node:PbNode; +begin + memoryTree := fileName = ''; + + // allocate the info section + getmem(info, sizeof(BTreeInfo)); + if (info = NIL) then exit; + fillchar(info^, sizeof(BTreeInfo), 0); + + with info^ do + begin + // setNull requires the memoryTree flag to be set + + setNull(root); + setNull(firstDeleted); + nodes := 1; + height := 0; + keys := 0; + bNodeSize := nodeSize; + treeType := tt; + end; {with} + + if not (memoryTree) then + begin + + assign(dataFile, filename); + if (fsearch(filename, '') <> '') then + begin + reset(dataFile, 1); + blockread(dataFile, info^, sizeof(BTreeInfo)); + end + else + begin + rewrite(dataFile, 1); + blockwrite(datafile, info^, sizeof(BTreeInfo)); + end; + reset(dataFile, info^.bNodeSize); + end; + + case info^.treeType of + BT_CUSTOM : InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + BT_PCHAR : InstallUserFunctions(compareKeyPChar, copyKeyPChar, keySizePChar); + BT_STRING : InstallUserFunctions(compareKeyString, copyKeyString, keySizeString); + BT_SINGLE : InstallUserFunctions(compareKeySingle, copyKeySingle, keySizeSingle); + BT_DOUBLE : InstallUserFunctions(compareKeyDouble, copyKeyDouble, keySizeDouble); + BT_INT32 : InstallUserFunctions(compareKeyInt32, copyKeyInt32, keySizeInt32); + BT_INT64 : InstalluserFunctions(compareKeyInt64, copyKeyInt64, keySizeInt64); + else + InstallUserFunctions(compareKeyNull, copyKeyNull, keySizeNull); + end; {case} + + + assign(f, 'output.txt'); + {assign(f, '');} + rewrite(f); +end; {bTree.init} + +function bTree.align; +begin + result := ((sizeof(uptr) + keyLength + 7) shr 3) shl 3; +end; {bTree.align} + +function bTree.allocEmptyNode; +var newNode:PbNode; +begin + setNull(result); + + if (not isNull(info^.firstDeleted)) then + begin + newNode := loadPage(info^.firstDeleted); + if (newNode = NIL) then exit; + info^.firstDeleted := newNode^.parent; + end + else + begin + getmem(newNode, info^.bNodeSize); + fillchar(newNode^, info^.bNodeSize, 0); + newNode^.tag := info^.nodes; + inc(info^.nodes); + end; + with newNode^ do + begin + magic := B_NODE_LEAF; + numKeys := 1; + size := dword(@newNode^.data) - dword(newNode); + setNull(left); + setNull(right); + setNull(parent); + setNull(reserved); + end; {with} + result := getAddress(newNode); + savePage(newNode); + discardPage(newNode); + +end; {bTree.allocEmptyNode} + +function bTree.calcSize; +var + size:integer; + curNode:integer; + dataNode:PbNodeData; +begin + if (node = NIL) then exit; + dataNode := @node^.data; + size := dword(dataNode) - dword(node); + + for curNode := 1 to node^.numKeys do + begin + inc(size, align(keySize(@dataNode^.key))); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + if (node^.magic = B_NODE_INDEX) then inc(size, sizeof(uPtr)); + result := size; +end; + +procedure bTree.deAllocNode; +begin + if (node = NIL) then exit; + with node^ do + begin + magic := B_NODE_OPEN; + setNull(left); + setNull(right); + setNull(reserved); + parent := info^.firstDeleted; + + size := dword(@node^.data) - dword(node); + fillchar(data, info^.bNodeSize - size, 0); + end; + info^.firstDeleted := getAddress(node); +end; {btree.deAllocNode} + +function bTree.deleteEntry; +var + bnode, tmpNode, neighbor, parent : PbNode; + neighborSize:integer; + + dataNode, nextDataNode, tmpDataNode:PbNodeData; + src, dst : pointer; + tmpLink:uPtr; + nodeSize:uInt32; + curNode, res, size: integer; + found : boolean; + + procedure getNeighbor; + var + l, r:PbNode; + begin + neighbor := NIL; + neighborSize := 0; + l := loadPage(bnode^.left); + r := loadPage(bnode^.right); + + if (l = NIL) then + begin + // this next check may be unecessary + if (r^.parent = bnode^.parent) then + begin + neighbor := r; + neighborSize := r^.size; + end; + exit + end; + + if (r = NIL) then + begin + // this next check may be unecessary + if (l^.parent = bnode^.parent) then + begin + neighbor := l; + neighborSize := l^.size; + end; + exit + end; + + if (l^.parent = r^.parent) then // left/right neighbors are children of the same parent + begin + if (l^.size < r^.size) then + begin + neighbor := l; + neighborSize := l^.size; + discardPage(r); + exit + end + else + begin + neighbor := r; + neighborSize := r^.size; + discardPage(l); + exit + end; + end + else // one of the neighbors has a different parent + begin + if (l^.parent = bnode^.parent) then + begin + neighbor := l; + neighborSize := l^.size; + discardPage(r); + exit + end + else + begin + neighbor := r; + neighborSize := r^.size; + discardPage(l); + exit + end; + end; + discardPage(l); + discardPage(r); + end; {getNeighbor local function} + +begin + + setNull(result); + + if (isNull(u)) then exit; + bnode := loadPage(u); + + if (bnode^.numKeys = 0) then halt; {this should never happen} + + if (bnode^.magic = B_NODE_LEAF) then + begin + case info^.treeType of + BT_STRING:writeln(f, 'Deleting ', string(key^), ' size: ', align(keySize(key))); + BT_PCHAR:writeln(f, 'Deleting ', pchar(key)); + end; + + dataNode := @bnode^.data; + curNode := 0; + repeat + res := compareKeys(@dataNode^.key, key); + if (res >= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until (curNode = bnode^.numKeys); + + if (res <> 0) then exit; + + result := dataNode^.link; + + nodeSize := align(keySize(@dataNode^.key)); + + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); + + move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode))); + dec(bnode^.numKeys); + dec(bnode^.size, nodeSize); + dataNode := pointer(dword(bnode) + bnode^.size); + fillchar(dataNode^, nodeSize, 0); + dec(info^.keys); + + // check for merges and other stuff + if (getAddress(bnode) = info^.root) then + begin + if (bnode^.numKeys = 0) then + begin + setNull(info^.root); + dec(info^.height); + deAllocNode(bnode); + exit; + end; + end + else {not root} + begin + if (bnode^.size < (info^.bNodeSize / 2)) then + begin + getNeighbor; + if (neighbor <> NIL) and (neighborSize <> 0) then + begin + if (bnode^.size + neighborSize <= info^.bNodeSize) then + begin + // if neighbor is a predecessor of bnode then swap them + if (getAddress(neighbor) = bnode^.left) then + begin + tmpNode := bnode; + bnode := neighbor; + neighbor := tmpNode; + end; + // merge the two nodes together + // bnode is the left node, neighbor is the right node + src := @neighbor^.data; + dst := pointer(dword(bnode) + bnode^.size); + + size := neighbor^.size - (dword(src) - dword(neighbor)); + + move(src^, dst^, size); + inc(bnode^.size, size); + inc(bnode^.numKeys, neighbor^.numKeys); + setRight(bnode^.left, getAddress(neighbor)); + bnode^.right := neighbor^.right; + move(bnode^, neighbor^, info^.bNodeSize); + savePage(neighbor); + deleteEntry(bnode^.parent, NIL, getAddress(bnode)); + discardPage(neighbor); + deAllocNode(bnode); + exit; + end //if (bnode^.size + neighborSize <= info^.bNodeSize) + else // "acquire" an entry from a neighbor + begin + + if (getAddress(neighbor) = bnode^.right) then + begin + // neighbor node is to the right. Append an entry from it to bnode + dataNode := @neighbor^.data; + src := dataNode; + dst := pointer(dword(bnode) + bnode^.size); + nodeSize := align(keySize(@dataNode^.key)); + move(src^, dst^, nodeSize); + inc(bnode^.size, nodeSize); + inc(bnode^.numKeys); + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); + move(nextDataNode^, + dataNode^, + neighbor^.size - (dword(dataNode) - dword(neighbor)) - nodeSize); + dec(neighbor^.size, nodeSize); + dec(neighbor^.numKeys); + + // clear off the end of the neighbor node + dataNode := pointer(dword(neighbor) + neighbor^.size); + fillchar(dataNode^, nodeSize, 0); + savePage(bnode); + savePage(neighbor); + dataNode := dst; + replaceEntry(bnode^.parent, @dataNode^.key, getAddress(bnode)); + discardPage(bnode); + discardPage(neighbor); + exit; + + end + else + begin + // neighbor node is to the left. Take the last entry and prepend to bnode + dataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + // dataNode (hopefully) now points to the last key/value pair in neighbor + nodeSize := align(keySize(@dataNode^.key)); + src := @bnode^.data; + dst := src; + inc(dst, nodeSize); + move(src^, dst^, bnode^.size - (dword(src) - dword(bnode))); + dst := src; + src := dataNode; + move(src^, dst^, nodeSize); + fillchar(src^, nodeSize, 0); + dec(neighbor^.size, nodeSize); + dec(neighbor^.numKeys); + inc(bnode^.size, nodeSize); + inc(bnode^.numKeys); + savePage(bnode); + savePage(neighbor); + // this can be optimized by storing the previous + // node from the for loop above + dataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + replaceEntry(neighbor^.parent, @dataNode^.key, getAddress(neighbor)); + discardPage(bnode); + discardPage(neighbor); + exit; + end; // else + end; + + end; //if (neighbor <> NIL) and (neighborSize <> 0) + end; // if (bnode^.size < (info^.bNodeSize / 2)) + end; // else not root + + end + else // not a leaf + begin + dataNode := @bnode^.data; + nextDataNode := dataNode; + nodeSize := align(keySize(@dataNode^.key)); + inc(nextDataNode, nodeSize); + found := FALSE; + for curNode := 1 to bnode^.numKeys do + begin + found := (dataNode^.link = value); + if (found) then break; + if (curNode <> bnode^.numKeys) then + begin + dataNode := nextDataNode; + nodeSize := align(keySize(@nextDataNode^.key)); + inc(nextDataNode, nodeSize); + end; + end; + + if (not found) then + begin + if (nextDataNode^.link = value) then + nextDataNode^.link := dataNode^.link + else + begin + writeln(f, 'didn''t find: ', hex(value.u)); + printInfo(getAddress(bnode)); + discardPage(bnode); + exit; + end; + end; + {if (dataNode = nextDataNode) then inc(nextDataNode, align(keySize(@dataNode^.key)));} + // Copy this link into the next link so we can just delete this node + {nextDataNode^.link := dataNode^.link;} + + fillchar(dataNode^, nodeSize, 0); + move(nextDataNode^, dataNode^, bnode^.size - (dword(nextDataNode) - dword(bnode))); + + dec(bnode^.numKeys); + dec(bnode^.size, nodeSize); + dst := pointer(dword(bnode) + bnode^.size); + fillchar(dst^, nodeSize, 0); + // check for merges and other stuff + if (getAddress(bnode) = info^.root) then + begin + if (bnode^.numKeys = 0) then + begin + dataNode := @bnode^.data; + info^.root := dataNode^.link; + dec(info^.height); + deAllocNode(bnode); + bnode := loadPage(info^.root); + setNull(bnode^.parent); + savePage(bnode); + discardPage(bnode); + exit + end; + end + else {not root} + begin + if (bnode^.size < (info^.bNodeSize / 2)) then + begin + getNeighbor; + if (neighbor<>NIL) and (neighborSize<>0) then + begin + if (bnode^.size + neighborSize <= info^.bNodeSize) then + begin + // if neighbor is a predecessor of bnode then swap them + if (getAddress(neighbor) = bnode^.left) then + begin + tmpNode := bnode; + bnode := neighbor; + neighbor := tmpNode; + end; + // We need to get the key that currently resides between + // the bnode (left) link and the neighbor (right) link in the parent + parent := loadPage(bnode^.parent); + if (parent = NIL) then halt; // this would be bad + + dataNode := @parent^.data; + for curNode := 0 to parent^.numKeys-1 do + begin + if (dataNode^.link = getAddress(bnode)) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + // theoretically dataNode^.key is the key between the two links + + size := align(keySize(@dataNode^.key)); + + if ((bnode^.size + neighbor^.size + size - (dword(@bnode^.data) - dword(bnode))) > info^.bNodeSize) then + begin + // The key wouldn't fit. bail + discardPage(parent); + discardPage(neighbor); + savePage(bnode); + discardPage(bnode); + exit; + end; + + // copy the key to the end of bnode + src := @dataNode^.key; + dst := pointer(dword(bnode) + bnode^.size); + move(src^, dst^, keySize(@dataNode^.key)); + inc(bnode^.numKeys); // we just added a key + + dec(size, sizeof(uPtr)); + inc(bnode^.size, size); // increment by aligned size + inc(dst, size); // also increment the dst by aligned size + + discardPage(parent); // don't need the parent any more + // merge the two nodes together + // bnode is the left node, neighbor is the right node + src := @neighbor^.data; + + size := neighbor^.size - (dword(src) - dword(neighbor)); + move(src^, dst^, size); + inc(bnode^.size, size); + inc(bnode^.numKeys, neighbor^.numKeys); + + dataNode := @bnode^.data; + for curNode := 0 to bnode^.numKeys do + begin + setParent(dataNode^.link, getAddress(neighbor)); + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + + setRight(bnode^.left, getAddress(neighbor)); + bnode^.right := neighbor^.right; + move(bnode^, neighbor^, info^.bNodeSize); + savePage(neighbor); + deleteEntry(bnode^.parent, NIL, getAddress(bnode)); + deAllocNode(bnode); + discardPage(neighbor); + + exit; + + end + else // "acquire" an entry from a neighbor + begin + // we need to do one extra check + if (getAddress(neighbor) = bnode^.right) then + begin + tmpLink := getAddress(bnode); + tmpDataNode := @neighbor^.data; + end + else + begin + tmpLink := getAddress(neighbor); + tmpDataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@tmpDataNode^.key))); + end; + + if (neighbor^.size - align(keySize(@tmpDataNode^.key)) < info^.bNodeSize / 2) then + begin + savePage(bnode); + discardPage(bnode); + discardPage(neighbor); + exit + end; + + // get the key between the neighbor/bnode pair + + parent := loadPage(bnode^.parent); + if (parent = NIL) then halt; // this would be bad + + dataNode := @parent^.data; + for curNode := 1 to parent^.numKeys do + begin + if (dataNode^.link = tmpLink) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + // theoretically dataNode^.key is the key between the two links + + nodeSize := align(keySize(@dataNode^.key)); + // do one last check before we borrow + + if (getAddress(neighbor) = bnode^.right) then + begin + savePage(neighbor); + + src := @dataNode^.key; + dataNode := @neighbor^.data; + insertEntry(getAddress(bnode), src, dataNode^.link); + replaceEntry(getAddress(parent), @dataNode^.key, getAddress(bnode)); + deleteEntry(getAddress(neighbor), NIL, dataNode^.link); + exit; + end + else + begin + // neighbor is to the left, bnode is to the right + savePage(bnode); + + // We need to take the key from the parent node, coupled with the + // last value in the neighbor, and insert them into bnode + // To do this we have to move everything in bnode over + src := @dataNode^.key; + nodeSize := align(keySize(@dataNode^.key)); + dataNode := @bnode^.data; + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); + move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bnode))); + + // copy in the key + fillchar(dataNode^, nodeSize, 0); + move(src^, dataNode^.key, keySize(src)); + // now we update the size and keys + inc(bnode^.size, nodeSize); + inc(bnode^.numKeys); + // we still have to insert the value and update the child's parent ptr + tmpDataNode := dataNode; + + dataNode := @neighbor^.data; + for curNode := 1 to neighbor^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + nextDataNode := dataNode; + inc(nextDataNode, align(keySize(@dataNode^.key))); + // insert the value + tmpDataNode^.link := nextDataNode^.link; + setParent(tmpDataNode^.link, getAddress(bnode)); + // need to change the insert (don't forget to update the child's parent ptr) + // insertEntry(getAddress(bnode), src, nextDataNode^.link); + replaceEntry(getAddress(parent), @dataNode^.key, getAddress(neighbor)); + deleteEntry(getAddress(neighbor), NIL, nextDataNode^.link); + exit; + + end; + end; + + end; + + end; + + end; + + end; {not a leaf} + + savePage(bnode); + discardPage(bnode); +end; {bTree.deleteEntry} + +procedure bTree.discardPage; +begin + // disk based trees allocate temporary memory to hold a page. So, if we're not + // a memory tree, dispose of the temp page + if not (memoryTree) then if (node <> NIL) then freemem(node, info^.bNodeSize); +end; {bTree.discardPage} + +function bTree.insertEntry; +var + tmpUPtr:uPtr; + dataNode, nextDataNode, prevDataNode:PbNodeData; + midKey:BTreeSearchRec; + keyLength, curNode, dataSize, res:integer; + bnode, newNode, rootNode:PbNode; + nodeSize:uInt32; +begin + result := FALSE; + if (key = NIL) then exit; + case info^.treeType of + BT_STRING:writeln(f, 'Inserting: ', string(key^)); + BT_PCHAR:writeln(f, 'Inserting: ', pchar(key)); + end; + + midKey.key := NIL; + midKey.keySize := 0; + + keyLength := keySize(key); + + if (keyLength = 0) or (keyLength > info^.bNodeSize) then exit; + + nodeSize := align(keyLength); + + if (isNull(info^.root)) then + begin + + (* + * This case should only occur once when a directory is first created. + *) + info^.root := allocEmptyNode; + if (isNull(info^.root)) then halt; + + rootNode := loadPage(info^.root); + + inc(info^.keys); + inc(info^.height); + rootNode^.magic := B_NODE_LEAF; + dataNode := @rootNode^.data; + + fillchar(dataNode^, nodeSize, 0); + + dataNode^.link := value; + copyKey(key, @dataNode^.key); + + inc(rootNode^.size, nodeSize); {hm.. should I add the sizeof the link?} + + savePage(rootNode); + discardPage(rootNode); + result := TRUE; + exit + end; + + bnode := loadPage(u); + + if (bnode^.size + nodeSize <= info^.bNodeSize) then + begin + + dataNode := @bnode^.data; + curNode := 0; + repeat + res := compareKeys(key, @dataNode^.key); + if (res <= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until curNode = bnode^.numKeys; + + if (res = 0) then + begin + dataNode^.link := value; + savePage(bnode); + discardPage(bnode); + result := TRUE; + exit; + end; + + nextDataNode := dataNode; + inc(nextDataNode, nodeSize); {cast to byte size records} + + move(dataNode^, nextDataNode^, bnode^.size - (dword(dataNode) - dword(bNode))); + + fillchar(dataNode^, nodeSize, 0); + copyKey(key, @dataNode^.key); + + dataNode^.link := value; + + if (bnode^.magic = B_NODE_INDEX) then + begin + if (not isNull(dataNode^.link)) then setParent(dataNode^.link, getAddress(bnode)); + { if (curNode = bnode^.numKeys) then + begin} + tmpUPtr := dataNode^.link; + dataNode^.link := nextDataNode^.link; + nextDataNode^.link := tmpUPtr; + { end;} + end; + inc(bnode^.numKeys); + + if (bnode^.magic = B_NODE_LEAF) then inc(info^.keys); + inc(bNode^.size, nodeSize); + savePage(bnode); + end + else + begin + + newNode := loadPage(allocEmptyNode); + if (newNode = NIL) then halt; + + newNode^.magic := bnode^.magic; + + newNode^.left := getAddress(bnode); + newNode^.right := bnode^.right; + newNode^.parent := bnode^.parent; + + if not(isNull(newNode^.right)) then setLeft(newNode^.right, getAddress(newNode)); + bnode^.right := getAddress(newNode); + + dataNode := @bnode^.data; + + {for curNode:=0 to (bnode^.numKeys div 2)-1 do + inc(dataNode, align(keySize(@dataNode^.key)));} + curNode := 1; + repeat + prevDataNode := dataNode; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until (dword(dataNode) - dword(bnode) >= bnode^.size shr 1); + + {midKey := dataNode;} + dataNode := prevDataNode; + midKey.keySize := keySize(@dataNode^.key); + GetMem(midKey.key, midKey.KeySize); + {if (midKey.key = NIL) then halt;} + copyKey(@dataNode^.key, midKey.key); + + dec(curNode); + + if (bnode^.magic = B_NODE_LEAF) then + begin + + { newNode^.left := getAddress(bnode); + newNode^.right := bnode^.right; + if not(isNull(newNode^.right)) then setLeft(newNode^.right, getAddress(newNode)); + bnode^.right := getAddress(newNode);} + + inc(dataNode, align(keySize(@dataNode^.key))); + + dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); + move(dataNode^, newNode^.data, dataSize); + fillchar(dataNode^, dataSize, 0); + + dec(bnode^.size, dataSize); + newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); + + {newNode^.numKeys := bnode^.numKeys - (bnode^.numKeys div 2)-1;} + newNode^.numKeys := bnode^.numKeys - curNode; + + dec(bnode^.numKeys, newNode^.numKeys); + + savePage(bnode); + savePage(newNode); + + if (compareKeys(key, midKey.key) <= 0) then + insertEntry(getAddress(bnode), key, value) + else + insertEntry(getAddress(newNode), key, value); + + end + else {not a leaf} + begin + + inc(dataNode, align(keySize(@dataNode^.key))); + + dataSize := bnode^.size - (dword(dataNode) - dword(bnode)); + move(dataNode^, newNode^.data, dataSize); + + fillchar(dataNode^, dataSize, 0); + + dec(bnode^.size, dataSize); + // we need to adjust the size because we're taking out the midkey + // and we're also adding that extra pointer + dec(bnode^.size, align(keySize(midkey.key))); + inc(bnode^.size, sizeof(uPtr)); + + newNode^.size := dataSize + (dword(@newNode^.data) - dword(newNode)); + {newNode^.numKeys := bnode^.numKeys - (bnode^.numKeys div 2)-1;} + newNode^.numKeys := bnode^.numKeys - curNode; + dec(bnode^.numKeys, newNode^.numKeys); + dec(bnode^.numKeys); + + savePage(bnode); + savePage(newNode); + + dataNode := @newNode^.data; + for curNode := 0 to newNode^.numKeys do + begin + setParent(dataNode^.link, getAddress(newNode)); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + //savePage(bnode); + + if (compareKeys(key, midKey.key) <= 0) then + insertEntry(getAddress(bnode), key, value) + else + insertEntry(getAddress(newNode), key, value); + + end; + + (* + * If we were the root node, then create a new one + *) + + if (getAddress(bnode) = info^.root) then + begin + + info^.root := allocEmptyNode; + rootNode := loadPage(info^.root); + setParent(getAddress(bnode), info^.root); + setParent(getAddress(newNode), info^.root); + + inc(info^.height); + {dataNode := @newNode^.data; } + dataNode := @rootNode^.data; + with rootNode^ do + begin + magic := B_NODE_INDEX; + {numKeys := 1;} + dataNode^.link := getAddress(bnode); + + keyLength := midKey.keySize; + copyKey(midKey.key, @dataNode^.key); + inc(size, align(keyLength)); + inc(size, sizeof(uPtr)); + inc(dataNode, align(keyLength)); + dataNode^.link := getAddress(newNode); + + end; {with} + savePage(rootNode); + discardPage(rootNode); + end {if oldNode = root} + else + begin + insertEntry(bnode^.parent, midKey.key, getAddress(newNode)); + end; + if (midKey.key <> NIL) and (midKey.keySize <> 0) then FreeMem(midKey.key, midKey.keySize); + discardPage(newNode); + end; {if out of space in this node} + discardPage(bnode); + result := TRUE +end; {bTree.insertEntry} + +function bTree.isNull; +begin + if (memoryTree) then + result := (u.bPtr = NIL) + else + result := (u.offset = -1) +end; {bTree.isNull} + +function bTree.loadPage; +var node:PbNode; +begin + + result := NIL; + if isNull(u) then exit; + + if (memoryTree) then + node := u.bPtr + else + begin + seek(dataFile, dword(u.offset)); + getmem(node, info^.bNodeSize); + blockread(dataFile, node^, 1); + end; + + result := node +end; {bTree.loadPage} + +function bTree.replaceEntry; +var + dataNode:PbNodeData; + node:PbNode; + curNode, res, srcKeySize, dstKeySize, deltaSize:integer; + src, dst:pointer; +begin + result := FALSE; + if ((key = NIL) or (isNull(u))) then exit; + node := loadPage(u); + if (node = NIL) then exit; + + if (node^.magic <> B_NODE_INDEX) then + begin + writeln('Error! Not in index nodes.'); + discardPage(node); + exit + end; + + dataNode := @node^.data; + // possible bug on count + for curNode := 0 to node^.numKeys do + begin + if (dataNode^.link = value) then + begin + srcKeySize := keySize(key); + dstKeySize := keySize(@dataNode^.key); + deltaSize := align(srcKeySize) - align(dstKeySize); + if (deltaSize = 0) then + begin + // source and dest keys are equal length. Replace old key with new key + fillchar(dataNode^.key, dstKeySize, 0); + move(key^, dataNode^.key, srcKeySize); + end + else + if (deltaSize < 0) then + begin + // source key is smaller than dest key. + fillchar(dataNode^.key, dstKeySize, 0); + move(key^, dataNode^.key, srcKeySize); + src := dataNode; + dst := dataNode; + inc(src, align(dstKeySize)); // no, this isn't backwards + inc(dst, align(srcKeySize)); // we're copying from the old size + move(src^, dst^, node^.size - (dword(src) - dword(node))); + inc(node^.size, deltaSize); // deltaSize is negative + end + else + begin + // source key is bigger than dest key + if ((deltaSize + node^.size) > info^.bNodeSize) then + begin + writeln('Source key would cause split'); + end + else // source key will fit + begin + // dataNode points to the value/key pair + src := dataNode; + dst := dataNode; + inc(src, align(dstKeySize)); + inc(dst, align(srcKeySize)); + move(src^, dst^, node^.size - (dword(src) - dword(node))); + fillchar(dataNode^.key, dstKeySize, 0); + move(key^, dataNode^.key, srcKeySize); + inc(node^.size, deltaSize); + end; + + end; + + break; + end; + inc(dataNode, align(keySize(@dataNode^.key))); + end; + discardPage(node); + result := TRUE; +end; {bTree.replaceEntry} + +procedure bTree.savePage; +begin + if (memoryTree) then exit; + + if (node = NIL) then exit; + seek(dataFile, dword(node^.tag)); + blockwrite(dataFile, node^, 1); + +end; {btree.savePage} + +procedure bTree.setLeft; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.left := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setLeft} + +procedure bTree.setParent; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.parent := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setParent} + +procedure bTree.setNull; +begin + if (memoryTree) then + u.offset := 0 + else + u.offset := -1; +end; {bTree.setNull} + +procedure bTree.setRight; +var node:PbNode; +begin + node := loadPage(u); + if (node = NIL) then exit; + node^.right := newPtr; + savePage(node); + discardPage(node); +end; {bTree.setRight} + +function bTree.fetchFirstKey; +var + node:PbNode; + dataNode:PbNodeData; +begin + result := FALSE; + if isNull(u) then exit; + node := loadPage(u); + if (node = NIL) then exit; + + dataNode := @node^.data; + if (node^.magic = B_NODE_LEAF) then + begin + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + if (searchRec.key = NIL) then exit; + + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + + result := TRUE; + end + else + begin + result := fetchFirstKey(dataNode^.link, searchRec); + end; + discardPage(node); +end; {bTree.fetchFirstKey} + +function bTree.fetchLastKey; +var + node:PbNode; + dataNode:PbNodeData; + curNode:integer; +begin + result := FALSE; + if isNull(u) then exit; + node := loadPage(u); + if (node = NIL) then exit; + + dataNode := @node^.data; + + if (node^.magic = B_NODE_LEAF) then + begin + for curNode := 1 to node^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + if (searchRec.key = NIL) then exit; + + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + result := TRUE; + end + else + begin + for curNode := 1 to node^.numKeys do + inc(dataNode, align(keySize(@dataNode^.key))); + result := fetchLastKey(dataNode^.link, searchRec); + end; + discardPage(node); +end; {bTree.fetchLastKey} + +procedure bTree.findLeafNode; +var + bNode:PbNode; + dataNode:PbNodeData; + i:integer; + link:uPtr; +begin + setNull(result); + if (key = NIL) then exit; + + if (isNull(u)) then exit; + + bnode := loadPage(u); + + if (bnode^.magic = B_NODE_LEAF) then + begin + result := u; + discardPage(bnode); + exit; + end + else + begin + dataNode := @bnode^.data; + for i:=0 to bnode^.numKeys -1 do + begin + if (compareKeys(key, @dataNode^.key) <= 0) then + begin + link := dataNode^.link; + discardPage(bnode); + result := findLeafNode(link, key); + exit; + end; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for} + link := dataNode^.link; + discardPage(bnode); + result := findLeafNode(link, key); + end; +end; {bTree.findLeafNode} + +procedure bTree.freeAll; +begin + if not(memoryTree) then exit; + freePage(info^.root); + setNull(info^.root); +end; {bTree.freeAll} + +procedure bTree.freePage; +var + curNode:integer; + dataNode:PbNodeData; + node : PbNode; +begin + + if isNull(u) then exit; + + node := u.bPtr; + curNode := 0; + if (node^.magic = B_NODE_INDEX) then + begin + dataNode := @node^.data; + for curNode := 0 to node^.numKeys-1 do + begin + freePage(dataNode^.link); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + freePage(dataNode^.link); + end; + freemem(node, info^.bNodeSize); +end; {bTree.freeAll} + +function bTree.getAddress; +var u:uPtr; +begin + setNull(u); + + if (memoryTree) then + u.bPtr := node + else + u.offset := node^.tag; { * bNodeSize;} + + result := u; +end; {bTree.getAddress} + +function bTree.Delete; +begin + result := deleteEntry(findLeafNode(info^.root, key), key, value); +end; {bTree.Delete} + +function bTree.Find; +var + bnode:PbNode; + dataNode:PbNodeData; + i, res:integer; + +begin + result := FALSE; + setNull(u); + + bnode := loadPage(findLeafNode(info^.root, key)); + if (bnode = NIL) then exit; {oops! serious problem} + + if (bnode^.magic <> B_NODE_LEAF) then writeln('Error! not in leaf nodes'); + + dataNode := @bnode^.data; + + for i:=0 to bnode^.numKeys-1 do + begin + res := compareKeys(@dataNode^.key, key); // strcomp(@dataNode^.key.str[1], pchar(key)); + if (res >= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for i} + + // did we find it? + if (res = 0) then + begin + u := dataNode^.link; + result := TRUE; + end; {if res = 0} + + discardPage(bnode); +end; {bTree.Find} + +procedure bTree.FindClose; +begin + with searchRec do + begin + setNull(value); + if (key<>NIL) then + FreeMem(key, align(keySize)); + key := NIL; + keySize := 0; + end; {with F} +end; {bTree.FindClose} + +function bTree.FindFirst; +var + node:PbNode; + dataNode:PbNodeData; + curNode, res:integer; +begin + result := FALSE; + + with searchRec do + begin + setNull(value); + key := NIL; + keySize := 0 + end; {with searchRec} + if (key = NIL) then exit; + node := loadPage(findLeafNode(info^.root, key)); + if (node = NIL) then exit; + + dataNode := @node^.data; + curNode := 0; + repeat + res := compareKeys(key, @dataNode^.key); + if (res <= 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until curNode = node^.numKeys; + + if (res = 0) then // we found it + begin + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + fillchar(searchRec.key^, align(searchRec.keySize), 0); + if (searchRec.key = NIL) then exit; + copyKey(@dataNode^.key, searchRec.key); + result := TRUE; + end; {if res = 0} + + discardPage(node); +end; {bTree.FindFirst} + +function bTree.FindNext; +var + nextNode:uPtr; + node:PbNode; + dataNode:PbNodeData; + curNode, res:integer; + +begin + result := FALSE; + + if (searchRec.key = NIL) then exit; + + node := loadPage(findLeafNode(info^.root, searchRec.key)); + if (node = NIL) then + begin + findClose(searchRec); + exit + end; + + dataNode := @node^.data; + curNode := 0; + repeat + res := compareKeys(@dataNode^.key, searchRec.key); + if (res > 0) then break; + inc(dataNode, align(keySize(@dataNode^.key))); + inc(curNode); + until curNode = node^.numKeys; + + if (res <= 0) and (curNode = node^.numKeys) then + begin + nextNode := node^.right; + discardPage(node); + node := loadPage(nextNode); + if (node = NIL) then + begin + findClose(searchRec); + exit + end; {node = NIL} + dataNode := @node^.data; + end; + + FreeMem(searchRec.key, align(searchRec.keySize)); + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + searchRec.key := NIL; + GetMem(searchRec.key, align(searchRec.keySize)); + // sanity check + if (searchRec.key <> NIL) then + begin + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + result := TRUE + end; + + discardPage(node); +end; {bTree.FindNext} + +function bTree.FindPrev; +var + prevNode:uPtr; + node:PbNode; + lastDataNode, dataNode:PbNodeData; + curNode, res:integer; + +begin + result := FALSE; + if (searchRec.key = NIL) then exit; + + node := loadPage(findLeafNode(info^.root, searchRec.key)); + if (node = NIL) then + begin + findClose(searchRec); + exit + end; + + dataNode := @node^.data; + lastDataNode := dataNode; + curNode := 0; + res := compareKeys(searchRec.key, @dataNode^.key); + + if (res > 0) then + repeat + lastDataNode := dataNode; + inc(curNode); + inc(dataNode, align(keySize(@dataNode^.key))); + res := compareKeys(searchRec.key, @dataNode^.key); + until (res <= 0) or (curNode = node^.numKeys); + + dataNode := lastDataNode; + + if ((res <= 0) and (curNode = 0)) then + begin + prevNode := node^.left; + discardPage(node); + node := loadPage(prevNode); + if (node = NIL) then + begin + FindClose(searchRec); + exit + end; + dataNode := @node^.data; + // scan to the end of the previous node. + // If there's only 1 key, this will do nothing + for curNode := 1 to node^.numKeys-1 do + inc(dataNode, align(keySize(@dataNode^.key))); + end; + + FreeMem(searchRec.key, align(searchRec.keySize)); + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + searchRec.key := NIL; + GetMem(searchRec.key, align(searchRec.keySize)); + // sanity check + if (searchRec.key <> NIL) then + begin + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + result := TRUE + end; + + discardPage(node) +end; {bTree.FindPrev} + +function bTree.GetFirstKey; +var + childLink:uPtr; + node:PbNode; + dataNode:PbNodeData; +begin + with searchRec do + begin + setNull(value); + key := NIL; + keySize := 0; + end; {with searchRec} + result := fetchFirstkey(info^.root, searchRec); +(* + dataNode := @node^.data; + while (node <> NIL) and (node^.magic <> B_NODE_LEAF) do + begin + childLink := dataNode^.link; + discardPage(node); + node := loadPage(childLink); + dataNode := @node^.data; + end; + + if (node = NIL) then exit; + + searchRec.value := dataNode^.link; + searchRec.keySize := keySize(@dataNode^.key); + GetMem(searchRec.key, align(searchRec.keySize)); + if (searchRec.key = NIL) then exit; + + fillchar(searchRec.key^, align(searchRec.keySize), 0); + copyKey(@dataNode^.key, searchRec.key); + + discardPage(node); + result := TRUE *) +end; {bTree.GetFirstKey} + +function bTree.GetLastKey; +var + childLink:uPtr; + node:PbNode; + dataNode:PbNodeData; + curNode:integer; +begin + with searchRec do + begin + setNull(value); + key := NIL; + keySize := 0; + end; {with searchRec} + result := fetchLastKey(info^.root, searchRec); +end; {bTree.GetLastKey} + +procedure bTree.PrintInfo; +const leafStr:array[boolean] of string[5] = ('INDEX', 'LEAF'); +var + dataNode:PbNodeData; + curNode:integer; + node : PbNode; + i:integer; +begin + node := loadPage(u); + if (node = NIL) then exit; + write(f, ' ', hex(dword(node^.parent.offset))); + writeln(f, ' ', leafStr[node^.magic = B_NODE_LEAF], ' (', hex(node^.magic), ')'); + write(f, hex(dword(node^.left.bPtr)), ' <-> ', hex(dword(node)), ' <-> ', hex(dword(node^.right.bPtr))); + writeln(f, ' | capacity ', node^.size, '[', calcSize(node), '](', info^.bNodeSize, ') | keys:', node^.numKeys); + if (node^.numKeys = 0) then exit; + dataNode := @node^.data; + + curNode := 0; + for curNode := 0 to node^.numKeys-(ord(node^.magic = B_NODE_LEAF)) do + begin + + write(f, '| ', curNode:2, '| '); + + {if (node^.magic = B_NODE_INDEX) then} + write(f, '[', hex(dword(dataNode^.link.offset)), ']'); + + if (curNode <> node^.numKeys) then + case info^.treeType of + BT_INT32:writeln(f, ' {', dataNode^.key.i, '} '); + BT_INT64:writeln(f, ' {', dataNode^.key.x, '} '); + BT_SINGLE:writeln(f, ' {', dataNode^.key.s, '} '); + BT_DOUBLE:writeln(f, ' {', dataNode^.key.d, '} '); + BT_STRING:writeln(f, ' {', dataNode^.key.str, '} '); + BT_PCHAR:begin + (*writeln(f, ' {', pchar(@dataNode^.key)^, '} '); *) + write(f, ' {'); + i:=0; + while (dataNode^.key.str[i]<>#0) do + begin + write(f, dataNode^.key.str[i]); + inc(i); + end; + writeln(f, '} '); + end; + end; + + inc(dataNode, align(keySize(@dataNode^.key))); + end; + discardPage(node); + writeln(f); +end; {bTree.PrintInfo} + +function bTree.Insert; +begin + result := insertEntry(findLeafNode(info^.root, key), key, value); +end; {bTree.Insert} + +procedure bTree.InstallUserFunctions; +begin + compareKeys := cmpFunc; + copyKey := copyProc; + keySize := ksFunc; +end; {bTree.InstallUserFunc} + +procedure bTree.Print; +var + curNode:integer; + dataNode:PbNodeData; + node : PbNode; + link : uPtr; +begin + if isNull(u) then exit; + PrintInfo(u); + node := loadPage(u); + curNode := 0; + if (node^.magic = B_NODE_INDEX) then + begin + dataNode := @node^.data; + for curNode := 0 to node^.numKeys-1 do + begin + Print(dataNode^.link); + inc(dataNode, align(keySize(@dataNode^.key))); + end; + Print(dataNode^.link); + end; + discardPage(node); +end; {bTree.Print} + +procedure bTree.PrintList; +var + dataNode:PbNodeData; + node:PbNode; + link:uPtr; + i : integer; +begin + + if (isNull(info^.root)) then exit; + + node := loadPage(info^.root); + while (node^.magic = B_NODE_INDEX) do + begin + dataNode := @node^.data; + link := dataNode^.link; + discardPage(node); + node := loadPage(link); + end; + + while (not isNull(link)) do + begin + dataNode := @node^.data; + for i := 0 to node^.numKeys -1 do + begin + writeln(f, dataNode^.key.str); + inc(dataNode, align(keySize(@dataNode^.key))); + end; {for i} + link := node^.right; + discardPage(node); + node := loadPage(link); + end; {while} + +end; {bTree.PrintList} + +procedure bTree.PrintWholeTree; +begin + Print(info^.root); + writeln(f, '---------------------------------------------------------------------------'); +end; {bTree.PrintWholeTree} + +destructor bTree.done; +begin + + if not(memoryTree) then + begin + reset(dataFile, 1); + blockwrite(dataFile, info^, sizeof(BTreeInfo)); + close(dataFile); + end; + + if (not isNull(info^.root)) then + with info^ do + begin + writeln('tree height: ', height); + writeln('tree leaf key count: ', keys); + writeln('Total number of nodes: ', nodes); + freeAll; + close(f); + end; + +end; {bTree.done} + +end. \ No newline at end of file diff --git a/master.lst b/master.lst new file mode 100755 index 0000000..e69de29 --- /dev/null +++ b/master.lst diff --git a/mytest.pas b/mytest.pas new file mode 100755 index 0000000..2c39ee9 --- /dev/null +++ b/mytest.pas @@ -0,0 +1,82 @@ +uses dos, crt, debug; +var f:file; + p:pointer; + count:integer; + dirRec:SearchRec; + name : string; +function search2(s:pathStr; dirList:string):string; +var + name: String; + ff_buf: SearchRec; + p:longint; +begin + length(result) := 0; + findfirst(s, anyFile, ff_buf); + if (dosError = 0) then result := ff_buf.name; +end; +function Search(Path: PathStr; Dirlist: String): PathStr; +var + name: String; + ff_buf: SearchRec; + + p: Longint; + +begin + (* Length(Result) := 0; + name := Path; + repeat + FindFirst('test.out', AnyFile, ff_buf); + if DosError = 0 then + begin + Result := name; + break; + end else begin + writeln('here'); + if Length(DirList) = 0 then break; + p := Pos(';', DirList); + if p <> 0 then + begin + name := Copy(DirList, 1, p - 1) + '\' + Path; + DirList := Copy(DirList, p + 1, 255); + end else begin + name := DirList + '\' + Path; + Length(DirList) := 0; + end + end + until FALSE; *) +end; + +begin + assign(f, 'test.out'); + rewrite(f, 1); + getmem(p, 4096); + blockwrite(f, p^, 4096); + freemem(p, 4096); + count := 0; +(* repeat + getmem(p, 4096); + // if (fsearch('test.out','') = '') then break; + + assign(f, 'test.out'); + reset(f, 1); + blockread(f, p^, 4096); + reset(f, 512); + blockread(f, p^, 8); + close(f); + freemem(p, 4096); + inc(count); + if (count and (1 shl 16-1) = 0) then writeln(count); + until keypressed; *) + repeat + name:='test.out'; + //search2(name,''); + //if (dosError<>0) then break; + //if (search('test.out','') = '') then break; + findFirst(name, anyFile, dirRec); + if (dosError<>0) then break; + findClose(dirRec); + inc(count); + if (count and (1 shl 16-1) = 0) then writeln(count); + until keypressed; + writeln(count); +end. \ No newline at end of file diff --git a/ram.dsk b/ram.dsk new file mode 100755 index 0000000..17681c5 --- /dev/null +++ b/ram.dsk Binary files differ diff --git a/testfile.dat b/testfile.dat new file mode 100755 index 0000000..06d7405 --- /dev/null +++ b/testfile.dat Binary files differ diff --git a/testq.pas b/testq.pas new file mode 100755 index 0000000..74dc780 --- /dev/null +++ b/testq.pas @@ -0,0 +1,14 @@ +uses lists; + +var + q:PQueue; + count:uInt32; +begin + new(q, init(5, sizeof(uInt32))); + write('Enqueuing numbers...'); + for count := 0 to 5 do + if not q^.enqueue(count) then writeln('Queue is full'); + writeln('done'); + while q^.dequeue(count) do writeln('dequeing: ', count); + dispose(q, done); +end. \ No newline at end of file diff --git a/testseek.pas b/testseek.pas new file mode 100755 index 0000000..0307494 --- /dev/null +++ b/testseek.pas @@ -0,0 +1,15 @@ +uses crt, dos; +var + f:file; + x:array[0..40] of byte; +begin + assign(f, 'testfile.dat'); + rewrite(f, 1); + blockwrite(f, x, sizeof(x)); + close(f); + reset(f, 1); + readkey; + seek(f, 1023); + blockwrite(f, x, 1); + close(f); +end. \ No newline at end of file diff --git a/testtree.dat b/testtree.dat new file mode 100755 index 0000000..9312abd --- /dev/null +++ b/testtree.dat Binary files differ diff --git a/timetest.pas b/timetest.pas new file mode 100755 index 0000000..2528da6 --- /dev/null +++ b/timetest.pas @@ -0,0 +1,72 @@ +var highTime:dword; + lowTime:dword; + x:int64; + y:int64; +function getTime:int64; assembler; +asm + xor edx, edx + xor eax, eax + rdtsc + mov lowTime, eax + mov highTime, edx +end; + +function foo:int64; +begin + result := 5347234290309; +end; +function bar:int64; +var x:int64; +begin + asm + mov dword ptr result, eax + mov dword ptr result+4, edx + end; + //result := x; +end; + +procedure getRDTSCTime(var x:int64); assembler; +asm + mov edi, x + rdtsc + mov [edi], eax + mov [edi+4], edx +end; + +procedure foobar(a:int64); +begin + writeln(a); +end; +begin +// repeat + x := getTime(); + writeln(highTime, ' : ', lowTime); +// write(' '); + longlongRec(x).hi := 1245; + longLongRec(x).lo := 6789; + writeln(x); + writeln(longLongRec(x).hi); + writeln(longLongRec(x).lo); + y := x; + writeln(longLongRec(y).hi); + writeln(longLongRec(y).lo); + + if (x <> y) then begin writeln('bad math'); end; + if (x <> foo()) then writeln('bad math 2'); + if (y <> bar()) then writeln('bad math 3'); + y := bar(); + writeln('--', y); + writeln(longLongRec(y).hi); + writeln(longLongRec(y).lo); + x := int64(65536) shl 17; + writeln(x); + foobar(x); + x := 2048; + if (x <> 0) then writeln('bad math 4'); + getRDTSCTime(x); + writeln(x); + getRDTSCTime(x); + writeln(x); + + // until keypressed; +end. \ No newline at end of file diff --git a/treetest.pas b/treetest.pas new file mode 100755 index 0000000..5d3c917 --- /dev/null +++ b/treetest.pas @@ -0,0 +1,10 @@ +uses lists; +var tree:PBTree; + u:uPtr; + s:string; +begin + new(tree, init('testtree.dat', 1024, BT_STRING, NIL)); + s := '.'; + tree^.insert(@s, u); + dispose(tree, done); +end. \ No newline at end of file diff --git a/uView.pas b/uView.pas new file mode 100755 index 0000000..d949204 --- /dev/null +++ b/uView.pas @@ -0,0 +1,65 @@ +program uView; + +uses + ObjGfx40, ogFont, DOS, CRT, Strings, UbixFS, DiskDrv; + +type + PUbixFSViewer = ^TUbixFSViewer; + TUbixFSViewer = object(TUbixFS) + private + device:PDiskDrive; + sb:PDiskSuperBlock; + public + constructor init(const filename:string); + procedure abort(const reason:string); + procedure run; + destructor done; virtual; + end; + +constructor TUbixFSViewer.init; +begin + new(device, init(filename)); + + inherited init(device); + + if not (screen^.ogCreate(1024, 768, OG_PIXFMT_32BPP)) then abort('Could not initialize graphics mode'); +end; // TUbixFSViewer.init + +procedure TUbixFSViewer.abort; +begin + dispose(screen, ogDone); + screen := NIL; + writeln(reason); + done(); + halt; +end; + +procedure TUbixFSViewer.Visualize; +begin + if (screen = NIL) then new(screen, ogInit); + +end; + +destructor TUbixFSViewer.done; +begin + inherited done; + if (device <> NIL) then dispose(device, done); +end; // TUbixFSViewer.done + +var + viewer:PUbixFSViewer; + +procedure usage; +begin + writeln('Usage:'); + writeln; + writeln('uView [filename]'); + halt; +end; // usage + +begin + if (paramcount <> 1) then usage; + new(viewer, init(paramstr(1))); + viewer^.run; + dispose(viewer, done); +end. \ No newline at end of file diff --git a/ubixfs.backup.pas b/ubixfs.backup.pas new file mode 100755 index 0000000..89d1846 --- /dev/null +++ b/ubixfs.backup.pas @@ -0,0 +1,1668 @@ +unit ubixfs; + +interface + +uses device, lists; + +const INODE_NO_FLAG = $00000000; +const INODE_IN_USE = $00000001; + +const ATTR_INODE = $00000004; +const INODE_LOGGED = $00000008; +const INODE_DELETED = $00000010; +const PERMANENT_FLAGS = $0000ffff; +const INODE_NO_CACHE = $00010000; +const INODE_WAS_WRITTEN = $00020000; +const S_IFDIR = $00040000; // POSIX +const NO_TRANSACTION = $00040000; + +const NUM_DIRECT_BLOCKS = 16; + +const UBIXFS_MAGIC_1 = $A0A0A0A; +const UBIXFS_MAGIC_2 = $B0B0B0B; +const UBIXFS_MAGIC_3 = $C0C0C0C; +const UBIXFS_INODE_MAGIC = $3BBE0AD9; + +const UBIXFS_CLEAN = $434C454E; +const UBIXFS_DIRTY = $44495254; + +const SECTOR_SHIFT = 9; +const SECTOR_SIZE = 1 shl SECTOR_SHIFT; + +type + int8 = shortInt; + int16 = smallInt; + int32 = longInt; + + uInt8 = byte; + uInt16 = word; + uInt32 = dWord; +{ Common Integer array definitions } + +type + int8ArrayPtr = ^int8Array; + int8Array = array[ 0..0 ] of int8; + + int16ArrayPtr = ^int16Array; + int16Array = array[ 0..0 ] of int16; + + int32ArrayPtr = ^int32Array; + int32Array = array[ 0..0 ] of int32; + + { Common Unsigned Integer array definitions } + +type + uInt8ArrayPtr = ^uInt8Array; + uInt8Array = array[ 0..0 ] of uInt8; + + uInt16ArrayPtr= ^uInt16Array; + uInt16Array = array[ 0..0 ] of uInt16; + + uInt32ArrayPtr= ^uInt32Array; + uInt32Array = array[ 0..0 ] of uInt32; + +type +(* TBlockRun = packed record + AG : int32; + start : uInt16; + len : uInt16; + end; + + inodeAddr = TblockRun; + + uPtr = packed record + case integer of + 0: (i : int32); + 1: (u : uInt32); + 2: (s : single); + 3: (d : double); + 4: (p : pointer); + 5: (offset : int64); + 6: (iAddr : inodeAddr); + end; +*) + PdiskSuperBlock = ^diskSuperBlock; + diskSuperBlock = packed record + name : array[0..31] of char; + magic1 : int32; + + fsByteOrder : int32; + + // blockSize on disk + blockSize : uInt32; + + // number of bits needed to shift a block number to get a byte address + blockShift : uInt32; + + numBlocks : int64; + usedBlocks : int64; + + inodeSize : uInt32; + magic2 : uInt32; + + blocksPerAG : uInt32; + AGShift : uInt32; + numAGs : uInt32; + + flags : uInt32; + + logBlocks : TBlockRun; + logStart : int64; + logEnd : int64; + + magic3 : int32; + BAT : inodeAddr; + rootDir : inodeAddr; + indicies : inodeAddr; + + pad : array[0..371] of byte; + end; // diskSuperBlock + + PDataStream = ^TDataStream; + TDataStream = packed record + direct : array[0..NUM_DIRECT_BLOCKS-1] of TBlockRun; + maxDirect : uInt32; + maxDirectU : uInt32; // upper 32 bits + indirect : TBlockRun; + maxIndirect : uInt32; + maxIndirectU : uInt32; // upper 32 bits + doubleIndirect: TBlockrun; + maxDIndirect : uInt32; + maxDIndirectU : uInt32; // upper 32 bits + size : uInt32; + sizeU : uInt32; // upper 32 bits + end; // TDataStream + + PubixfsInode = ^ubixfsInode; + ubixfsInode = packed record + magic : int32; + inodeNum : inodeAddr; + uid : uInt32; + gid : uInt32; + mode : int32; + flags : int32; + createTime : int64; + modifiedTime : int64; + parent : inodeAddr; + attributes : inodeAddr; + iType : uInt32; + inodeSize : uInt32; + data : uPtr; {this was the etc field in bfs} + refCount : uInt32; + blocks : TDataStream; + blockCount : uInt32; + smallData : array[0..0] of byte; + end; // ubixfsInode + + PfileDescriptor = ^TfileDescriptor; + TfileDescriptor = packed record + magic : uInt32; + inode : pointer; + offset : int64; + size : int64; + + end; // fileDescriptor + + vfs_abstract = object + private + prev : ^vfs_abstract; + next : ^vfs_abstract; + dev : PDevice; + public + constructor init; + destructor done; virtual; + end; // vfs_abstract + + PUbixFS = ^TUbixFS; + TUbixFS = object(vfs_abstract) + private + superBlock : ^diskSuperBlock; + freeBlockList : PfileDescriptor; + InodeCache : PbTree; + procedure assert(x:boolean); + function allocBRun(blockRun:TBlockRun):TBlockRun; + function allocInode(parentIAddr:inodeAddr):PUbixfsInode; + function extendFile(inode:PubixfsInode; numBlocks:uInt32):integer; + function findBRun(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunDirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunDIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function freeUsedBlocks(blockRun:TBlockRun):integer; + function getFileSize(inode:PUbixfsInode):uInt32; // limits to 4GB + function getFileSpace(inode:PUbixfsInode):uInt32; // limits to 4GB + function iAddrToStr(iAddr:inodeAddr):string; + function isValidIAddr(iAddr:inodeAddr):boolean; + function loadInode(iaddr:inodeAddr):PUbixfsInode; + function markUsedBlocks(blockRun:TBlockRun):integer; + function nextAG(AG:uInt32):uInt32; + function pathToIAddr(path:PChar):inodeAddr; + + function readDataStream(inode:PubixfsInode; position:uInt32; buf:pointer; length:uInt32):integer; // position limits files to 4GB + function saveInode(inode:PUbixfsInode):integer; + procedure setBlockRun(var blockRun:TBlockRun; AG:uInt32; start, len:uInt16); + procedure setIAddr(var inode:inodeAddr; AG:uInt32; start, len:uInt16); + function strToIAddr(s:string):inodeAddr; + function unloadInode(inode:PubixfsInode):integer; + function writeDataStream(inode:PubixfsInode; position:uInt32; buf:pointer; length:uInt32):integer; // position limits files to 4GB + public + constructor init(device:Pdevice); + procedure printSuperBlock; + function vfs_fCreate(filename:PChar):int32; + function vfs_fExist(path:PChar):integer; + function vfs_format(device:PDevice; fsBlockSize:uInt32; quick:boolean):integer; + function vfs_init:integer; + function vfs_mkdir(path:PChar):integer; + function vfs_read(filedes:int64; position:uInt32; buffer:pointer; size:uInt32):uInt32; // limits to 4GB + function vfs_write(filedes:int64; position:uInt32; buffer:pointer; size:uInt32):uInt32; // limits to 4GB + procedure examine; + destructor done; virtual; + end; // TUbixFS + +implementation + +uses strings, dos, math, lists; + +type + PUbixBTreeVFS = ^TUbixBTreeVFS; + TUbixBTreeVFS = object(TBTreeVFS) + private + fs : PUbixFS; + inode : PUbixFSInode; + position : uInt32; // limits files to 4GB + inodeSize : uInt32; + public + constructor init(_fs:PUbixFS); + function fClose:boolean; virtual; + function fCreate(const filename:string):boolean; virtual; + function fExist(const filename:string):boolean; virtual; + function fOpen(const filename:string):boolean; virtual; + function fRead(var buf; size:uInt32):boolean; virtual; + function fSeek(offset:uInt32):boolean; virtual; + function fWrite(var buf; size:uInt32):boolean; virtual; + destructor done; virtual; + end; // TUbixBTreeVFS + +constructor TUbixBTreeVFS.init; +begin +// writeln('TUbixBTreeVFS.init()'); + fs := _fs; + inode := NIL; + inodeSize := 0; + position := 0; +end; // TUbixBTreeVFS.init; + +function TUbixBTreeVFS.fClose; +begin + if (fs = NIL) or (inode = NIL) then exit; + fs^.unloadInode(inode); + inode := NIL; + inodeSize := 0; + position := 0; +end; // TUbixBTreeVFS.fClose + +function TUbixBTreeVFS.fCreate; +begin + // I *think* that this code is only called for the + // B^Tree. Therefore, the file technically exists, but just + // has nothing in it when we call fCreate. + result := fOpen(filename); +end; // TUbixBTreeVFS.fCreate + +function TUbixBTreeVFS.fExist; +var + tmpIAddr:inodeAddr; + tmpInode:PUbixfsInode; +begin + result := FALSE; + assert(fs <> NIL); + if (fs = NIL) then exit; + assert(fs^.superBlock <> NIL); + if (fs^.superBlock = NIL) then exit; + tmpIAddr := fs^.strToIAddr(filename); + if not fs^.isValidIAddr(tmpIAddr) then exit; + + // If we've created an inode, but haven't written any data to it, then + // the B^Tree hasn't written out a superblock, so technically the file doesn't + // exist, and this is a really long run-on sentence. + tmpInode := fs^.loadInode(tmpIAddr); + result := (tmpInode <> NIL) and (tmpInode^.blocks.size <> 0); + fs^.unloadInode(tmpInode); +end; // TUbixBTreeVFS.fExist + +function TUbixBTreeVFS.fOpen; +var + iAddr : inodeAddr; +begin + result := FALSE; + if (fs = NIL) then exit; + if (fs^.superBlock = NIL) then exit; + iAddr := fs^.strToIAddr(filename); + position := 0; + inode := fs^.loadInode(iAddr); + result := (inode <> NIL); +end; // TUbixBTreeVFS.fOpen + +function TUbixBTreeVFS.fRead; +begin + result := FALSE; + if (fs = NIL) or (inode = NIL) then exit; +// with inode^.inodeNum do +// write('fRead(', AG, ':', start, ':', len, ', '); +// write(position, ', '); +// write('@buf, '); + + result := (fs^.readDataStream(inode, position, @buf, size) = 0); + inc(position, size); +end; // TUbixBTreeVFS.fRead + +function TUbixBTreeVFS.fSeek; +begin + // uhm.. if the desired offset is beyond the end of the file, + // should we not extend out the file? Or does that occur in writeDataStream ... + position := offset; + result := TRUE; +end; // TUbixBTreeVFS.fSeek + +function TUbixBTreeVFS.fWrite; +begin + result := FALSE; + if (fs = NIL) or (inode = NIL) then exit; +// with inode^.inodeNum do +// write('fWrite(', AG, ':', start, ':', len, ', '); +// write(position, ', '); +// write('@buf, '); + result := (fs^.writeDataStream(inode, position, @buf, size) = 0); + inc(position, size); +end; // TUbixBTreeVFS.fWrite; + +destructor TUbixBTreeVFS.done; +begin + if ((inode <> NIL) and (inodeSize <> 0)) then FreeMem(inode, inodeSize); + inodeSize := 0; + inode := NIL; + fs := NIL; // don't destruct it since we didn't allocate it +end; // TUbixBTreeVFS.done + +constructor vfs_abstract.init; +begin + next := NIL; + prev := NIL; + dev := NIL; +end; // vfs_abstract.init + +destructor vfs_abstract.done; +begin +end; // vfs_abstract.done + +constructor TUbixFS.init; +begin + inherited init; + freeBlockList := NIL; + inodeCache := NIL; + superBlock := NIL; + dev := device; +end; // TUbixFS.init + +procedure TUbixFS.assert(x:boolean); +begin + if not x then runerror(1000); +end; // TUbixFS.assert + +function TUbixFS.allocBRun; +var + inode:PUbixfsInode; + FBL:int8ArrayPtr; + FBLSize:uInt32; + bitOffset:uInt32; + byteOffset:uInt32; + curBlock, baseBlock:uInt32; + freeCount:uInt32; +begin + setBlockRun(result, 0, 0, 0); + if (superBlock = NIL) then exit; + if (blockRun.AG > superBlock^.numAGs) then exit; + if (superBlock^.usedBlocks + blockRun.len > superBlock^.numBlocks) then exit; // should probably give a louder warning + inode := loadInode(superBlock^.BAT); + assert(inode <> NIL); // should probably check for failure + // now that the BAT inode is loaded, we can scan through it looking for + // a free bit + + // we read in one block at a time + FBLSize := superBlock^.blockSize; + GetMem(FBL, FBLSize); + assert(FBL <> NIL); + fillchar(FBL^, FBLSize, 0); // better safe than sorry + + assert(readDataStream(inode, blockRun.AG shl (superblock^.AGShift shr 3), FBL, FBLSize) = 0); + byteOffset := blockRun.start shr 3; + bitOffset := 7-(blockRun.start and 7); + baseBlock := (blockRun.AG shl superBlock^.AGShift) + blockRun.start; + curBlock := baseBlock; + freeCount := 0; + // an optimization would be to check to see if the byte is -1, thus skipping 8 at a time + // This will need to be implemented (but can be later) + repeat + + inc(curBlock); + + if (FBL^[byteOffset] and (1 shl bitOffset) = 0) then + inc(freeCount) + else + freeCount := 0; + + if (freeCount = blockRun.len) then break; + + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + + if (byteOffset = superBlock^.blocksPerAG) then + begin + byteOffset := 0; + curBlock := 0; + inc(blockRun.AG); + if (blockRun.AG = superBlock^.numAGs) then blockRun.AG := 0; + assert(readDataStream(inode, blockRun.AG shl (superblock^.AGShift shr 3), FBL, FBLSize) = 0); + bitOffset := 7; + end; + + until (curBlock = baseBlock); + + if (freeCount = blockRun.len) then // we found a run + begin + blockRun.AG := (curBlock-blockRun.len) shr superBlock^.AGShift; + blockRun.start := (curBlock-blockRun.len) and (superBlock^.blocksPerAG-1); + markUsedBlocks(blockRun); + result := blockRun; + end + else writeln('Failure in allocBRun()'); + FreeMem(FBL, FBLSize); + assert(unloadInode(inode) = 0); + +end; // TUbixFS.allocBRun + +function TUbixFS.allocInode; +var + inode:PUbixfsInode; + inodeSize:uInt32; + blockRun:TBlockRun; + +begin + // XXX ToDo: + // need to fill in the rest of the fields in the inode record + + result := NIL; + if (superBlock = NIL) then exit; + assert(parentIAddr.AG < superBlock^.numAGs); + if (parentIAddr.AG >= superBlock^.numAGs) then exit; // parent addr is outside out block count? + // ask for a single block somewhere around the beginning of the requested AG + setBlockRun(blockRun, parentIAddr.AG, 0, 1); + blockRun := allocBRun(blockRun); + assert(blockRun.len <> 0); + if (blockRun.len = 0) then exit; // this would be bad + inodeSize := superBlock^.inodeSize; + GetMem(inode, inodeSize); + assert(inode <> NIL); + if (inode = NIL) then exit; + fillchar(inode^, inodeSize, 0); + + with inode^ do + begin + magic := UBIXFS_INODE_MAGIC; + + // inodes point to themselves + inodeNum := blockRun; + + uid := 0; // this doesn't mean much here + gid := 0; + // mode := ?? + flags := INODE_NO_FLAG; + // createTime := + // modifiedTime := + + parent := parentIAddr; + setIAddr(attributes, 0, 0, 0); + inodeSize := superBlock^.inodeSize; + // data := 0; + refCount := 0; // maybe 1? + // there's no data yet in the file, so don't + // set blocks to anything (cleared above) + blockCount := 0; + + end; // with + result := inode; +end; // TUbixFS.allocInode + +function TUbixFS.extendFile; +var + blockRun, exBlockRun:TBlockRun; + i:integer; + found:boolean; +begin + // XXX ToDo: + // Need to add support for indirect and double-indirect blocks + // Fix 4GB issue + // Check to see if the block extension is the last one in direct blocks + + result := -1; // failure by default + // basic sanity checks + if (inode = NIL) or (superBlock = NIL) then exit; + + // if the file size is 0 then this is the first extension. + if (inode^.blocks.size = 0) then // first extension + begin + // a file's data is stored in the next allocation group from it's parent + // inode. This will help distribute the data across the disk. + setBlockRun(exBlockRun, nextAG(inode^.inodeNum.AG), 0, numBlocks); + exBlockRun := allocBRun(exBlockRun); // try to allocate a blockrun + assert(exBlockRun.len <> 0); + if (exBlockRun.len = 0) then exit; // erp. Couldn't extend the file + inode^.blocks.direct[0] := exBlockRun; // Set the first direct run + // The space allocated for the file can and may be different than the + // actual size. Set the maxDirect to the size (in bytes) of how much space + // we allocated + inode^.blocks.maxDirect := numBlocks shl superBlock^.blockShift; + end // size = 0 + else + begin + // the size of the file wasn't 0 (handled above). We need to extend the + // file out. Find where the last blocks of the file are + + // inode^.blocks.size is now a 32-bit integer because the compiler doesn't + // properly support 64-bit ints. Therefore, we are limited to 4GB + blockRun := findBRun(@inode^.blocks, inode^.blocks.size shr superBlock^.blockShift); + assert(blockRun.len <> 0); // hm.. was exBlockRun + if (blockRun.len = 0) then exit; // This would be odd + exBlockRun := blockRun; // copy the last run to exBlockRun + exBlockRun.len := numBlocks; // set the requested number of blocks + exBlockRun := allocBRun(exBlockRun); // allocate a block run near the previous one + if (exBlockRun.len = 0) then exit; // should probably yell louder about this + + // blockRun points to the last blockRun structure associated with this inode + if (exBlockRun.AG = blockRun.AG) and (exBlockRun.start = blockRun.start+1) then + begin + // the extended block run continues a previous run. + // scan through to find what the last run was + + with inode^, blocks do + begin + // XXX only works for direct block runs for now + // I'm going to have to put a special check in to see if + // we're the last entry in the direct field + if (direct[high(direct)].len <> 0) then assert(false); + + for i := low(direct) to high(direct) do + if (direct[i].len = 0) then break; + assert(i <> low(direct)); // big problemo + // make sure we don't overflow the length + if (dword(direct[i-1].len) + numBlocks < 65535) then + begin + inc(direct[i-1].len, numBlocks); // update the previous entry + end + else // oops, we overflowed the length + begin + // need to check to see if we were the last direct entry. If we were + // then we need to allocate an indirect block and store it in there + if (i = high(direct)) then assert(false); + direct[i] := exBlockRun; + end; // else + end; // with + end + else // extended blockRun isn't contiguous to the previous one + begin + + with inode^, blocks do + begin + // XXX only works for direct block runs for now + + // I'm going to have to put a special check in to see if + // we're the last entry in the direct field + if (direct[high(direct)].len <> 0) then assert(false); + + for i := low(direct) to high(direct) do + if (direct[i].len = 0) then break; + // i points to the entry in the direct[] block runs to the first free slot + direct[i] := exBlockRun; // set this entry to the extended block run + // now update the maxDirect size + maxDirect := maxDirect + numBlocks shl superBlock^.blockShift; + end; // with + end; // else + end; // else size <> 0 + + inc(inode^.blockCount, numBlocks); // update the blockCount in the inode + // no matter where we stored the extended blockRun, we have updated the inode + + assert(saveInode(inode) = 0); // 0 result means success + result := 0; // no error +end; // TUbixFS.extendFile + +function TUbixFS.findBRun; +var + position:uInt32; // limits filesize to 4gb +begin + setBlockRun(result, 0, 0, 0); + if (dataStream = NIL) then exit; + position := blockNum shl superBlock^.blockShift; + if (position < dataStream^.maxDirect) then + result := findBRunDirect(dataStream, blockNum) + else if (position < dataStream^.maxIndirect) then + result := findBRunIndirect(dataStream, blockNum) + else if (position < dataStream^.maxDIndirect) then + result := findBRunDIndirect(dataStream, blockNum) + else writeln('Block position is not mapped by dataStream'); +end; // TUbixFS.findBRun + +function TUbixFS.findBRunDirect; +var + maxBlock:uInt32; + sum, i, offset:uInt32; +begin + // set the result to a nil blockrun + setBlockRun(result, 0, 0, 0); + + // maxDirect is in bytes, so shift it down to blocks + maxBlock := dword(dataStream^.maxDirect) shr superblock^.blockShift; + + if (blockNum > maxBlock) then + begin + writeln('Requested block outside direct block run range'); + exit; + end; + + sum := 0; + with dataStream^ do + for i := low(direct) to high(direct) do + with direct[i] do + begin + if (blockNum < sum + len) then // found it + begin + offset := blockNum - sum; + setBlockRun(result, AG, start + offset, len - offset); + exit + end; + inc(sum, len); + end; // for +end; // TUbixFS.findBRunDirect + +function TUbixFS.findBRunIndirect; +begin + // XXX ToDo: + // load and scan the indirect block for the corresponding block run + setBlockRun(result, 0, 0, 0); +end; // TUbixFS.findBRunIndirect + +function TUbixFS.findBRunDIndirect; +begin + // XXX ToDo: + // find the position in the double-indirect range + // load the appropiate double-indirect block and get the blockRun + + setBlockRun(result, 0, 0, 0); +end; // TUbixFS.findBRunDIndirect + +function TUbixFS.freeUsedBlocks; +var + inode:PUbixfsInode; + FBL:int8ArrayPtr; + FBLSize:uInt32; + startByte, endByte:uInt32; + bitCount, bitOffset:uInt32; + startBlock:uInt32; + byteOffset:uInt32; +begin + + result := -1; + // basic sanity checks + if ((superBlock = NIL) or (blockRun.len = 0)) then exit; + + // first thing to do is load the BAT inode + inode := loadInode(superBlock^.BAT); + assert(inode <> NIL); + + // Okay.. inode is the BAT inode. We need this to read from/write to the BAT + + // Compute how much of the BAT we have to read in. + with blockRun, superBlock^ do + begin + startBlock := (AG shl AGShift) + start; + startByte := startBlock shr 3; // divide by 8 since it's a bitfield + endByte := (startBlock + len) shr 3; // divide by 8 since it's a bitfield + FBLSize := endByte - startByte + 1; + + GetMem(FBL, FBLSize); + assert(readDataStream(inode, startByte, FBL, FBLSize) = 0); + bitOffset := 7-(startBlock and 7); + byteOffset := 0; + + for bitCount := 1 to len do + begin + FBL^[byteOffset] := FBL^[byteOffset] and not (1 shl bitOffset); + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + end; // for bitCount + // save back out the modified block + assert(writeDataStream(inode, startByte, FBL, FBLSize) = 0); + + FreeMem(FBL, FBLSize); + assert(unloadInode(inode) = 0); + usedBlocks := usedBlocks - len; + end; // with + + result := 0; +end; // TUbixFS.freeUsedBlocks + +function TUbixFS.getFileSize; +begin + // XXX ToDo: + // Fix to support sizes > 4GB + result := 0; + if (inode = NIL) then exit; + // inode^.blocks.size is now a 32-bit integer. + // This limits the filesize to 4GB + result := inode^.blocks.size; +end; // TUbixFS.getFileSize + +function TUbixFS.getFileSpace; +begin + // getFileSpace() will return the size of the file's allocated space + // XXX ToDo: + // Fix to support sizes > 4GB + result := 0; + if (inode = NIL) or (superBlock = NIL) then exit; + result := inode^.blockCount shl superBlock^.blockShift; +end; // TUbixFS.getFileSpace + +function TUbixFS.iAddrToStr; +var s:string; +begin + // Note: this won't work in C/C++ because strings are really null + // terminated character arrays. So, the B^Tree won't hold a string + // with nulls in it properly + length(s) := sizeof(iAddr); + move(iAddr, s[1], sizeof(iAddr)); + result := s; +end; // TUbixFS.iAddrToStr; + +function TUbixFS.loadInode; +var + u:uPtr; + inode:PUbixfsInode; +begin + // XXX ToDo: + // Read through file cache + // Do more sanity checks on the inode + + result := NIL; + if (superBlock = NIL) or (dev = NIL) or (inodeCache = NIL) then exit; + + if inodeCache^.Find(@iAddr, u) then // inode was in cache + begin + inode := u.p; + assert(inode <> NIL); + end + else + begin + // allocate space for the inode + GetMem(inode, superBlock^.inodeSize); + assert(inode <> NIL); + // clear it (this isn't absolutely necessary, but better + // safe than sorry). Also, it's only done the first time + // the inode is loaded, so speed isn't as much a concern + fillchar(inode^, superBlock^.inodeSize, 0); + with iAddr do + writeln('inode: ', AG, ':', start, ':', len); + with iAddr, superBlock^ do + dev^.read(dev, + inode, + ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT), + inodeSize shr SECTOR_SHIFT + ); // dev^.read + // should probably check more than this + if (inode^.magic <> UBIXFS_INODE_MAGIC) then + begin + FreeMem(inode, superBlock^.inodeSize); + writeln('TUbixFS.loadInode() failed to load an inode!'); + exit; + end; + // now insert the pointer into the InodeCache + u.p := inode; + assert(inodeCache^.Insert(@iAddr, u)); + end; + inc(inode^.refCount); // increment the reference count + result := inode; +end; // TUbixFS.loadInode + +function TUbixFS.markUsedBlocks; +var + inode:PUbixfsInode; + FBL:uInt8ArrayPtr; + FBLSize:uInt32; + startByte, endByte:uInt32; + bitCount, bitOffset:uInt32; + startBlock:uInt32; + byteOffset:uInt32; +begin + + result := -1; + // basic sanity checks + if ((superBlock = NIL) or (blockRun.len = 0)) then exit; +with blockRun do +writeln('---- marking used blocks: ', AG, ':', start, ':', len); + + // first thing to do is load the BAT inode + inode := loadInode(superBlock^.BAT); + assert(inode <> NIL); + + // Okay.. inode is the BAT inode. We need this to read from/write to the BAT + + // Compute how much of the BAT we have to read in. + with blockRun, superBlock^ do + begin + startBlock := (AG shl AGShift) + start; + startByte := startBlock shr 3; + endByte := (startBlock + len) shr 3; + FBLSize := endByte - startByte + 1; + GetMem(FBL, FBLSize); + assert(readDataStream(inode, startByte, FBL, FBLSize) = 0); + fillchar(FBL^, FBLSize, 0); // better safe than sorry + bitOffset := 7-(startBlock and 7); + byteOffset := 0; + for bitCount := 1 to len do + begin + FBL^[byteOffset] := FBL^[byteOffset] or (1 shl bitOffset); + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + end; // for bitCount + assert(writeDataStream(inode, startByte, FBL, FBLSize) = 0); + + FreeMem(FBL, FBLSize); + assert(unloadInode(inode) = 0); + + usedBlocks := usedBlocks + len; + end; // with + result := 0; +end; // TUbixFS.markUsedBlocks + +function TUbixFS.nextAG; +begin + // there's no error we can throw from here if superblock is NIL, + // so don't bother to check. Just hope whomever calls this + // function has done some basic sanity checks + result := (AG+1) mod superBlock^.numAGs +end; // TUbixFS.nextAG + +function TUbixFS.pathToIAddr; + + function recursePath(path:PChar; len:integer; iAddr:inodeAddr):inodeAddr; + var + dirTree:PBTree; + u:uPtr; + nameSize:integer; + begin + // if the string length is 0 then we have found the inode in the previous + // recurse, so return what we found. + if (len = 0) then + begin + result := iAddr; + exit; + end + else + setIAddr(result, 0, 0, 0); + // hm. If we're looking for something like ``/.'' then the ``/'' is stripped + // off in the main call leaving only ``.'', which means that this malformed + // path check would trigger. Same with /foo/bar if bar is a dir. + //if uInt8ArrayPtr(path)^[0] = ord('/') then exit; // malformed path + if not (isValidIAddr(iAddr)) then exit; // sanity check + nameSize := 0; + while (len > 0) and (uInt8ArrayPtr(path)^[nameSize] <> ord('/')) do + begin + inc(nameSize); + dec(len); + end; + uInt8ArrayPtr(path)^[nameSize+1] := 0; + if (len <> 0) then dec(len); + + new(dirTree, open(IAddrToStr(iAddr), new(PUbixBTreeVFS, init(@self)))); + if (dirTree = NIL) then exit; // hm.. what happens to the PUbixBTreeVFS is this is NIL? + // note that the dirTree is a tree of BT_PCHAR, and searches are case sensitive. + // if the tree doesn't exist, Find() should return false. + if dirTree^.Find(path, u) then result := recursePath(path + nameSize + 1, len, u.iAddr); + dispose(dirTree, done); + end; // recursePath + +var len:integer; +begin + setIAddr(result, 0, 0, 0); + + if (path = NIL) or (superBlock = NIL) then exit; + + len := strlen(path); + if (len = 0) then exit; // empty string + if uInt8ArrayPtr(path)^[0] <> ord('/') then exit; // malformed path + + result := recursePath(path+1, len-1, superBlock^.rootDir); + +end; // TUbixFS.pathToIAddr + +procedure TUbixFS.printSuperBlock; +begin + if (superBlock = NIL) then exit; + with superBlock^ do + begin + writeln('superBlock^.name........... ', name); + writeln('superBlock^.magic1......... ', hex(magic1)); + writeln('superBlock^.fsByteOrder.... ', fsByteOrder); + writeln('superBlock^.blockSize...... ', blockSize); + writeln('superBlock^.blockShift..... ', blockShift); + writeln('superBlock^.numBlocks...... ', numBlocks); + writeln('superBlock^.usedBlocks..... ', usedBlocks); + writeln('superBlock^.magic2......... ', hex(magic2)); + writeln('superBlock^.blocksPerAG.... ', blocksPerAG); + writeln('superBlock^.AGShift........ ', AGShift); + writeln('superBlock^.numAGs......... ', numAGs); + writeln('superBlock^.flags.......... ', hex(flags)); + writeln('superBlock^.magic3......... ', hex(magic3)); + with superBlock^.BAT do + writeln('superBlock^.BAT............ ', AG, ':', start, ':', len); + with superBlock^.rootDir do + writeln('superBlock^.rootDir........ ', AG, ':', start, ':', len); + end; +end; // TUbixFS.printSuperBlock + +function TUbixFS.saveInode; +begin + // XXX ToDo: + // Write through cache + result := -1; + if (superBlock = NIL) or (dev = NIL) or (inode = NIL) then exit; + + with inode^.inodeNum, superBlock^ do + dev^.write(dev, + inode, + ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT), + inodeSize shr SECTOR_SHIFT + ); // device^.write + result := 0; +end; // TUbixFS.saveInode + +function TUbixFS.isValidIAddr; +begin + result := (iAddr.len <> 0) and (iAddr.AG < superBlock^.numAGs); +end; // TUbixFS.isValidIAddr + +function TUbixFS.readDataStream; +var + tmpBlock:uInt8ArrayPtr; + blockRun:TBlockRun; + size, remainder:uInt32; + blockStart:uInt32; + blocksToRead:uInt32; + bytesToRead:uInt32; +begin + // XXX ToDo: + // Read from cache + + result := -1; + if ((inode = NIL) or (buf = NIL)) then exit; + tmpBlock := NIL; + size := length; + remainder := position mod superBlock^.blockSize; + + if (remainder <> 0) then + begin + // the position we've been asked to read from doesn't align to + // a block size. We need to read in the previous block and pad + // the data + blockStart := position shr superBlock^.blockShift; + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + GetMem(tmpBlock, superBlock^.blockSize); + assert(tmpBlock <> NIL); + if (tmpBlock = NIL) then exit; + + with superBlock^, blockRun do + dev^.read(dev, + tmpBlock, + ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT), + blockSize shr SECTOR_SHIFT + ); // dev^.write + + bytesToRead := min(size, (superBlock^.blockSize - remainder)); + move(tmpBlock^[remainder], buf^, bytesToRead); + inc(buf, bytesToRead); + dec(size, bytesToRead); + inc(position, bytesToRead); + end; // if remainder != 0 + + remainder := size mod superBlock^.blockSize; + dec(size, remainder); + + // Size is currently in bytes. The remainder variable above holds the number + // of remaining bytes (if any) outside full blocks we have to write. + + // convert the size into blocks + size := size shr superBlock^.blockShift; + + while (size > 0) do + begin + blockRun := findBRun(@inode^.blocks, position shr superBlock^.blockShift); + assert(blockRun.len > 0); + blocksToRead := min(blockRun.len, size); + with inode^, superBlock^, blockRun do + dev^.read(dev, + buf, + ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT), + blockSize shr SECTOR_SHIFT + ); // dev^.read + dec(size, blocksToRead); + inc(buf, blocksToRead shl superBlock^.blockShift); + end; // while + + if (remainder <> 0) then + begin + // There is less than a block of data left to read. + blockStart := (position+length-remainder) shr superBlock^.blockShift; + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + if (tmpBlock = NIL) then GetMem(tmpBlock, superBlock^.blockSize); + assert(tmpBlock <> NIL); + with inode^, superBlock^, blockRun do + dev^.read(dev, + tmpBlock, + ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT), + blockSize shr SECTOR_SHIFT + ); // dev^.read + move(tmpBlock^, buf^, remainder); + end; + if (tmpBlock <> NIL) then FreeMem(tmpBlock, superBlock^.blockSize); + result := 0; +end; // TUbixFS.readDataStream + +procedure TUbixFS.setBlockRun; +begin + blockRun.AG := AG; + blockRun.start := start; + blockRun.len := len; +end; // TUbixFS.setBlockRun + +procedure TUbixFS.setIAddr; +begin + inode.AG := AG; + inode.start := start; + inode.len := len; +end; // TUbixFS.setIAddr + +function TUbixFS.strToIAddr; +begin + // Note: this won't work in C/C++ because strings are really null + // terminated character arrays. So, the B^Tree won't hold a string + // with nulls in it properly + // begin fiendishly clever hack + move(s[1], result, sizeof(inodeAddr)); + // end fiendishly clever hack +end; // TUbixFS.strToIAddr + +function TUbixFS.unloadInode; +var + u:uPtr; +begin + // XXX ToDo: + // I guess this would be a good place to determine if the + // file has been deleted, and free all resources associated + // with it after the ref count drops to 0 + result := -1; + if (inode = NIL) or (inodeCache = NIL) then exit; + + dec(inode^.refCount); // decrese the reference count + if (inode^.refCount = 0) then + begin + // the last handle to the inode was closed, so de-allocate + // the memory for the inode and delete it out of the cache + u.p := inode; + inodeCache^.Delete(@inode^.inodeNum, u); + // we have inodes in the cache, but they might have been modified + // The proper solution to this is to not have an inode cache and + // instead use a real block cache and write out modified blocks. + // For the time being, save out this inode to the device + saveInode(inode); + { if (dev <> NIL) then + with inode^.inodeNum, superBlock^ do + dev^.write(dev, + inode, + ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT), + inodeSize shr SECTOR_SHIFT + ); // device^.write } + FreeMem(inode, inode^.inodeSize); + end; + result := 0; +end; // TUbixFS.unloadInode + +function TUbixFS.vfs_fCreate; +var + inode:PUbixFSInode; + dirTree:PBTree; + dirIAddr, fileIAddr:inodeAddr; + filedes:int64; + u:uPtr; + searchRec:BTreeSearchRec; + len : integer; + path:PChar; + filenamePtr:PChar; + curPos:integer; +begin + result := -1; + if (filename = NIL) or (superBlock = NIL) then exit; + // we need to split the filename into it's component path and filename pieces. + // From there we need to see if the file exists. If it does then I guess we + // should truncate the file and load the inode. Otherwise allocate a new inode + len := strlen(filename); + if (len = 0) then exit; + if uInt8ArrayPtr(filename)^[0] <> ord('/') then exit; // malformed path + GetMem(path, len+1); + strCopy(path, filename); + curPos := len; + + setIAddr(dirIAddr, 0, 0, 0); // zero out the inode address for the directory + setIAddr(fileIAddr, 0, 0, 0); // zero out the inode address for the file + + while (curPos > 0) and (uInt8ArrayPtr(path)^[curPos] <> ord('/')) do dec(curPos); + fileNamePtr := path + curPos + 1; + if (curPos = 0) then + begin + // file is in the root dir + writeln('path: /'); + writeln('filename: ', fileNamePtr); + dirIAddr := pathToIAddr('/'); + end + else + begin + uInt8ArrayPtr(path)^[curPos] := 0; // null out the separator + writeln('Path: ', path); + writeln('Filename: ', fileNamePtr); + dirIAddr := pathToIAddr(path); + end; + + with dirIAddr do + writeln('iaddr to path: ', AG, ':', start, ':', len); + assert(isValidIAddr(dirIAddr)); + if not isValidIAddr(dirIAddr) then exit; + // should probably check to make sure that we're opening a directory and not a file + write('creating new dir tree...'); + new(dirTree, open(IAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self)))); + writeln('done'); + if (dirTree = NIL) then exit; + if dirTree^.Find(fileNamePtr, u) then + begin + writeln(fileNamePtr, ' already exists'); + end + else + begin + writeln('creating new file: ', fileNamePtr); + writeln('allocating new inode'); + inode := allocInode(dirIAddr); // alloc a new inode (pass in the parent iaddr) + assert(inode <> NIL); // make sure it isn't nil + if (inode = NIL) then exit; + saveInode(inode); // save the new inode + u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr + // now add the file to the directory + writeln('Inserting ', fileNamePtr, ' into directory returned ', dirTree^.Insert(fileNamePtr, u)); + end; + dispose(dirTree, done); + FreeMem(path, len+1); + result := 0; +end; // TUbixFS.vfs_fCreate + +function TUbixFS.vfs_fExist; +begin + // XXX ToDo: + // Find out why fExist('/.') fails + result := -1; + if ((superBlock = NIL) or (path = NIL)) then exit; + if (not isValidIAddr(superBlock^.rootDir)) then exit; + if isValidIAddr(pathToIAddr(path)) then result := 0; +end; // TUbixFS.vfs_fExist + +function TUbixFS.vfs_format; +var + sector:array[0..511] of byte; + fs:PUbixFS; + logicalBlocks, fsBATSize, fsBATBlocks:uInt32; + sb:PdiskSuperBlock; + inode:PubixfsInode; + i, fsBlockShift, lBlockShift, nodeSize, keyLength:integer; + blockRun:TBlockRun; + remainderBlocks:uInt32; + blockBit:shortint; +begin + result := -1; + if (device = NIL) then exit; + if (fsBlockSize < 1024) then + begin + writeln('Blocksize must be >= 1024'); + exit; + end; + // zero out the sector + fillchar(sector, sizeof(sector), 0); + + writeln('device^.sectors: ', device^.sectors); + + if not quick then + begin + system.write('clearing device... '); + + for i := 0 to device^.sectors-1 do + device^.write(device, @sector, i, 1); + + writeln('done'); + end; // if !quick + + + fsBlockShift := 10; {minimum is 1024} + while (1 shl fsBlockShift < fsBlockSize) do inc(fsBlockShift); + + // allocate a new superBlock and clear it + + new(sb); + if (sb = NIL) then exit; + fillchar(sb^, sizeof(diskSuperBlock), 0); + + // device^.sectors is the number of 512 byte sectors + + lBlockShift := fsBlockSize shr 9; + writeln('lBlockShift: ', lBlockShift); + logicalBlocks := device^.sectors div lBlockShift; // logical blocks + writeln('logical blocks: ', logicalBlocks); + + with sb^ do + begin + strcopy(name, 'UbixFS'); + magic1 := UBIXFS_MAGIC_1; + fsByteOrder := 0; + blockSize := fsBlockSize; + blockShift := fsBlockShift; + numBlocks := logicalBlocks; + usedBlocks := 0; + inodeSize := fsBlockSize; + magic2 := UBIXFS_MAGIC_2; + blocksPerAG := min(65536, blockSize shl 3); + AGShift := 10; // realistically it should be a min of 13 + while (1 shl AGShift <> blocksPerAG) do inc(AGShift); + numAGs := (dword(numBlocks)+(blocksPerAG-1)) div blocksPerAG; + + flags := UBIXFS_CLEAN; + setBlockRun(logBlocks, 0, 0, 0); + logStart := 0; + logEnd := 0; + magic3 := UBIXFS_MAGIC_3; + setIAddr(indicies, 0, 0, 0); + setIAddr(rootDir, 0, 0, 0); + end; // with + // create the free block list inode + + getmem(inode, fsBlockSize); // make the inode the same size as a block + if (inode = NIL) then exit; + + fillchar(inode^, sb^.inodeSize, 0); + + with inode^ do + begin + magic := UBIXFS_INODE_MAGIC; + + // inodes point to themselves + setIAddr(inodeNum, 0, sb^.logBlocks.len+1, 1); // the +1 for the start might be wrong + setIAddr(parent, 0, 0, 0); + + // If the partition is larger than 550GB we will need to allocate more than + // one blockRun for the BAT. + fsBATSize := logicalBlocks shr 3; // compute how many bytes the BAT takes up + fsBATBlocks := (fsBATSize + fsBlockSize-1) div fsBlockSize; + writeln('fsBATSize: ', fsBATSize); + writeln('fsBATBlocks: ', fsBATBlocks); + blockCount := fsBATBlocks; + setBlockRun(blocks.direct[0], 0, inodeNum.start+1, fsBATBlocks); + + blocks.size := (fsBATBlocks * fsBlockSize); // round the file size up to the nearest block size + blocks.maxDirect := blocks.size-1; + uid := 0; + gid := 0; + mode := S_IFDIR; + + flags := INODE_NO_FLAG; + + setIAddr(attributes, 0, 0, 0); + inodeSize := sb^.inodeSize; + + end; {with} + sb^.BAT := inode^.inodeNum; + writeln('writing out superblock to sector 1'); + device^.write(device, sb, 1, 1); + // note that the superblock does not contain proper inode addresses for the + // BAT and rootDir. Those are sync'd to disk in the destructor after they've been + // created below. + + system.write('Writing out BAT inode... '); + // write out the Block Allocation Table inode + with inode^.inodeNum, sb^ do + device^.write(device, + inode, + ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT), + inodeSize shr SECTOR_SHIFT + ); // device^.write + writeln('done'); + // Okay, the superblock is the only piece of information needed + // to mount an UbixFS partition + new(fs, init(device)); + assert(fs <> NIL); + assert(fs^.vfs_init() = 0); + + // The freshly formatted parition is now mounted. + // From here we need to create: + // free block list file contents + // journal + // root dir + + // inode still points to the BAT. Clear out the file in case people decided to + // use quick formatting + for i := 0 to (dword(inode^.blocks.size) div sizeof(sector))-1 do + fs^.writeDataStream(inode, i*sizeof(sector), @sector, sizeof(sector)); + setBlockRun(blockRun, 0, 0, 1); + fs^.markusedBlocks(blockRun); // mark superBlock block as used + // mark the journal as used here + fs^.markUsedBlocks(inode^.inodeNum); // mark the BAT inode as used + // now mark the BAT file blocks as used. Note that if the BAT takes up + // more than one blockRun (unlikely, but not impossible) then this code + // will have to check for that. + writeln('BAT inode blocks.direct[0].len: ', inode^.blocks.direct[0].len); + fs^.markUsedBlocks(inode^.blocks.direct[0]); + + // If the number of blocksPerAG doesn't divide evenly into the + // number of logical blocks, mark out all the "remainder" blocks + // We need to mark the blocks as used in the FBL manually because + // if we do it through markUsedBlocks() it will change the usedBlocks + // count. + + remainderBlocks := (fsBATBlocks shl sb^.blockShift) - fsBATSize; + writeln('RemainderBlocks: ', remainderBlocks); + if (remainderBlocks <> 0) then + begin + // check if the remainder blocks are evenly divisible by 8 + if (remainderBlocks and 7 <> 0) then + begin + blockBit := uInt8(1 shl (remainderBlocks and 7)) -1; + fs^.writeDataStream(inode, fsBatSize, @blockBit, 1); + blockBit := -1; + for i := fsBatSize+1 to (fsBATBlocks shl sb^.blockShift)-1 do + fs^.writeDataStream(inode, i, @blockBit, 1); + end + else + begin + blockBit := -1; + for i := fsBatSize to (fsBATBlocks shl sb^.blockShift)-1 do + fs^.writeDataStream(inode, i, @blockBit, 1); + end; + end; + + fs^.vfs_mkdir('/'); + //writeln(fs^.vfs_fExist('/.')); // <- this blows up!? + + fs^.printSuperBlock; + + dispose(fs, done); + writeln('Format complete!'); + dispose(sb); + freemem(inode, fsBlockSize); + +end; // TUbixfS.vfs_format + +function TUbixFS.vfs_init; +begin + // This function should probably not be called more than once without + // calling the appropiate shutdown code + writeln('TUbixFS.vfs_init()'); + result := -1; + if (dev = NIL) then exit; + new(superBlock); + assert(superBlock <> NIL); + if (superBlock = NIL) then exit; + fillchar(superBlock^, sizeof(superBlock^), 0); // better safe than sorry + // read in the superBlock. It's always the 1st block on the partition (counting from 0) + system.write('reading in superBlock... '); + dev^.read(dev, superBlock, 1, 1); + writeln('done'); + + assert(superBlock^.magic1 = UBIXFS_MAGIC_1); + assert(superBlock^.magic2 = UBIXFS_MAGIC_2); + assert(superBlock^.magic3 = UBIXFS_MAGIC_3); + assert(strcomp(superBlock^.name, 'UbixFS') = 0); + assert((1 shl superBlock^.blockShift) = superBlock^.blockSize); + assert((1 shl superBlock^.AGShift) = superBlock^.blocksPerAG); + assert(superBlock^.flags = UBIXFS_CLEAN); + result := 0; // success + + // create the file decriptor tree + new(inodeCache, init('', 256, BT_INT64, NIL)); +end; // TUbixFS.vfs_init + +function TUbixFS.vfs_mkdir; +const + dot:array[0..1] of char = '.'#0; + dotdot:array[0..2] of char = '..'#0; +var + inode:PUbixFSInode; + iAddr:inodeAddr; + bRun:TBlockRun; + len:integer; + dirTree:PbTree; + u:uPtr; +begin + // XXX ToDo: + // Ability to add sub directories + + result := -1; + assert(path <> NIL); + assert(superBlock <> NIL); + if (path = NIL) or (superBlock = NIL) then exit; + + len := strlen(path); + assert(len <> 0); + if (len = 0) then exit; + + if (len = 1) and (uInt8ArrayPtr(path)^[0] = ord('/')) then + begin + writeln('Creating root directory'); + // wanting to create the root. Check to see if the root dir inode is empty + assert(not isValidIAddr(superBlock^.rootDir)); + if isValidIAddr(superBlock^.rootDir) then exit; // should set a proper error code here + setIAddr(iAddr, 0, 0, 0); // technically the "parent" iaddr + inode := allocInode(iAddr); // allocate an inode (parent points to la-la land) + assert(inode <> NIL); // make sure it isn't nil + if (inode = NIL) then exit; + superBlock^.rootDir := inode^.inodeNum; // assign the root dir + saveInode(inode); // save the inode + u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr + + // open the root dir + with superBlock^.rootDir do + writeln('Root dir inode: ', AG, ':', start, ':', len); + writeln('Opening root dir tree'); + new(dirTree, init(iAddrToStr(superBlock^.rootDir), 1024, BT_PCHAR, new(PUbixBTreeVFS, init(@self)))); + writeln('Inserting ''.'''); + // Get ready to insert the '.' :: inode^.inodeNum key :: value pair + writeln('dirTree^.Insert(@dot, u) returned ', dirTree^.Insert(@dot, u)); // '.' points to ourself + dispose(dirTree, done); // close and save the directory +// saveInode(inode); // save the inode + freemem(inode, inode^.inodeSize); // free the memory associate with the inode (saved above) + end + else + begin + // subdir support goes here + end; + + result := 0; +end; // TUbixFS.vfs_mkdir + +function TUbixFS.vfs_read; +var + inode:PUbixFSInode; + u:uPtr; +begin + result := -1; + if (buffer = NIL) or (superBlock = NIL) or (inodeCache = NIL) then exit; + if not inodeCache^.Find(@filedes, u) then exit; + inode := u.p; + if (inode = NIL) then exit; + assert(readDataStream(inode, position, buffer, size) = 0); + result := size; +end; // TUbixFS.vfs_read + +function TUbixFS.vfs_write; +var + inode:PUbixFSInode; + u:uPtr; +begin + result := -1; + if (buffer = NIL) or (inodeCache = NIL) then exit; + if not inodeCache^.Find(@filedes, u) then exit; + inode := u.p; + if (inode = NIL) then exit; + assert(writeDataStream(inode, position, buffer, size) = 0); + result := size; +end; // TUbixFS.vfs_write + + +function TUbixFS.writeDataStream; +var + tmpBlock:uInt8ArrayPtr; + blockRun:TBlockRun; + size, remainder:uInt32; + blockStart:uInt32; + blocksToWrite:uInt32; + bytesToWrite:uInt32; + inodeChanged:boolean; +begin + // XXX ToDo: + // Read/Write through cache + // fix block extension size + result := -1; + if ((inode = NIL) or (buf = NIL) or (superBlock = NIL)) then exit; + tmpBlock := NIL; + + inodeChanged := FALSE; + if (position + length > getFileSpace(inode)) then + begin + // the next line is slightly wrong. We extend 4 blocks, + // but we may be writing out more than 4 blocks worth of + // data. + extendFile(inode, 4); // XXX pick a reasonable amount to extend by + inode^.blocks.size := position+length; // update the blocks + writeln('inode^.blocks.size = ', inode^.blocks.size); + inodeChanged := TRUE; + end + else + if (position + length > inode^.blocks.size) then + begin + // The above case checks to see if we're writing out past + // the end of the allocated space on the device. If we do, we extend + // out the file. However, if we're just writing past the end of + // the file, we have to update the size. + inodeChanged := TRUE; + inode^.blocks.size := position+length; + end; + + size := length; + remainder := position mod superBlock^.blockSize; + + if (remainder <> 0) then + begin + // the position we've been asked to write to doesn't align to + // a block size. We need to read in the previous block and pad + // the data + blockStart := position shr superBlock^.blockShift; + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + GetMem(tmpBlock, superBlock^.blockSize); + assert(tmpBlock <> NIL); + if (tmpBlock = NIL) then exit; + // read in the block + readDataStream(inode, blockStart, tmpBlock, superBlock^.blockSize); + bytesToWrite := min(size, (superBlock^.blockSize - remainder)); + move(buf^, tmpBlock^[remainder], bytesToWrite); + with inode^, superBlock^, blockRun do + dev^.write(dev, + tmpBlock, + ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT), + blockSize shr SECTOR_SHIFT + ); // dev^.write + inc(buf, bytesToWrite); + dec(size, bytesToWrite); + inc(position, bytesToWrite); + end; // if remainder != 0 + + remainder := size mod superBlock^.blockSize; + dec(size, remainder); + + // Size is currently in bytes. The remainder variable above holds the number + // of remaining bytes (if any) outside full blocks we have to write. + + // convert the size into blocks + size := size shr superBlock^.blockShift; + + while (size > 0) do + begin + blockRun := findBRun(@inode^.blocks, position shr superBlock^.blockShift); + assert(blockRun.len > 0); + blocksToWrite := min(blockRun.len, size); + with inode^, superBlock^, blockRun do + dev^.write(dev, + buf, + ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT), + blockSize shr SECTOR_SHIFT + ); // dev^.write + dec(size, blocksToWrite); + inc(buf, blocksToWrite shl superBlock^.blockShift); + end; // while + + if (remainder <> 0) then + begin + // There is less than a block of data left to write. Read in the old block + blockStart := (position+length-remainder) shr superBlock^.blockShift; + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + if (tmpBlock = NIL) then GetMem(tmpBlock, superBlock^.blockSize); + assert(tmpBlock <> NIL); + if (tmpBlock = NIL) then exit; + // read in the block + readDataStream(inode, blockStart, tmpBlock, superBlock^.blockSize); + move(buf^, tmpBlock^[0], remainder); + with inode^, superBlock^, blockRun do + dev^.write(dev, + tmpBlock, + ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT), + blockSize shr SECTOR_SHIFT + ); // dev^.write + end; + if (tmpBlock <> NIL) then FreeMem(tmpBlock, superBlock^.blockSize); + if (inodeChanged) then saveInode(inode); + result := 0; +end; // TUbixFS.writeDataStream + +destructor TUbixFS.done; +var + searchRec:BTreeSearchRec; + inode:PUbixfsInode; +begin + inherited done; + if (InodeCache <> NIL) then + begin + if (InodeCache^.getFirstKey(searchRec)) then + repeat + inode := searchRec.value.p; + if (inode <> NIL) then unloadInode(inode); + //if (inode <> NIL) then freemem(inode, inode^.inodeSize); + until not InodeCache^.findNext(searchRec); + writeln('inodes in InodeCache: ', InodeCache^.GetKeyCount()); + dispose(InodeCache, done); + InodeCache := NIL; + end; + // we need to sync the superblock back out to disk + // Doing this should probably be done in sync() instead of here. + // We should also probably honour the clean/dirty flags and only + // write it out when needed. + if (dev <> NIL) then dev^.write(dev, superBlock, 1, 1); + if (superBlock <> NIL) then dispose(superBlock); +end; // TUbixFS.done + +procedure TUbixFS.examine; +var + dirTree:PBTree; + searchRec:BTreeSearchRec; + u:uPtr; +begin + writeln('------------- examining FS -----------------'); + write('Opening root dir...'); + new(dirTree, open(IAddrToStr(superblock^.rootDir), new(PUbixBTreeVFS, init(@self)))); + //dirTree^.PrintList('rootDir.txt'); + writeln('done'); + writeln('Root directory has ', dirTree^.getKeyCount(), ' entrie(s)'); + write('Getting first key...'); + dirTree^.getFirstKey(searchRec); + writeln('done'); + writeln('key: ', pchar(searchRec.key)^); + with searchRec.value.iAddr do + writeln('value: ', AG, ':', start, ':', len); + dispose(dirTree, done); + writeln('------------------ done --------------------'); +end; + +end. \ No newline at end of file diff --git a/ubixfs.dsk b/ubixfs.dsk new file mode 100755 index 0000000..4f02496 --- /dev/null +++ b/ubixfs.dsk Binary files differ diff --git a/ubixfs_backup.pas b/ubixfs_backup.pas new file mode 100755 index 0000000..d1ae3c9 --- /dev/null +++ b/ubixfs_backup.pas @@ -0,0 +1,3464 @@ +unit UbixFS; + +interface +{$DEFINE GRAPHICS} +uses + device, lists {$IFDEF GRAPHICS}, ObjGfx40, ogFont, objImage, DOS, CRT {$ENDIF}; + + // open/fcntl - O_SYNC is only implemented on blocks devices and on files + // located on an ext2 file system +const O_ACCMODE = 00000003; +const O_RDONLY = 00000000; +const O_WRONLY = 00000001; +const O_RDWR = 00000002; +const O_APPEND = 00000010; +const O_CREAT = 00000400; // not fcntl +const O_TRUNC = 00001000; // not fcntl +const O_EXCL = 00002000; // not fcntl +const O_LARGEFILE = 00004000; +const O_SYNC = 00100000; + +// O_NONBLOCK = 00200004; // HPUX has separate NDELAY & NONBLOCK */ +//const O_NDELAY = O_NONBLOCK; +const O_NOCTTY = 00400000; // not fcntl */ +const FASYNC = 00020000; // fcntl, for BSD compatibility */ +const O_DIRECT = 00040000; // direct disk access hint - currently ignored */ +const O_DIRECTORY = 00010000; // must be a directory */ +const O_NOFOLLOW = 00000200; // don't follow links */ +const O_INVISIBLE = 04000000; // invisible I/O, for DMAPI/XDSM + +const F_DUPFD = 0; // dup +const F_GETFD = 1; // get f_flags +const F_SETFD = 2; // set f_flags +const F_GETFL = 3; // more flags (cloexec) +const F_SETFL = 4; +const F_GETLK = 5; +const F_SETLK = 6; +const F_SETLKW = 7; +const F_GETLK64 = 8; +const F_SETLK64 = 9; +const F_SETLKW64 = 10; + + // for F_[GET|SET]FL +const FD_CLOEXEC = 1; // actually anything with low bit set goes + // for posix fcntl() and lockf() +const F_RDLCK = 01; +const F_WRLCK = 02; +const F_UNLCK = 03; + + // for old implementation of bsd flock () +const F_EXLCK = 4; // or 3 +const F_SHLCK = 8; // or 4 + + // for leases +const F_INPROGRESS = 16; + + // operations for bsd flock(), also used by the kernel implementation +const LOCK_SH = 1; // shared lock +const LOCK_EX = 2; // exclusive lock +const LOCK_NB = 4; // or'd with one of the above to prevent blocking +const LOCK_UN = 8; // remove lock + +const LOCK_MAND = 32; // This is a mandatory flock +const LOCK_READ = 64; // ... Which allows concurrent read operations +const LOCK_WRITE = 128; // ... Which allows concurrent write operations +const LOCK_RW = 192; // ... Which allows concurrent read & write ops + +const INODE_NO_FLAG = $00000000; +const INODE_IN_USE = $00000001; + +const ATTR_INODE = $00000004; +const INODE_LOGGED = $00000008; +const INODE_DELETED = $00000010; +const PERMANENT_FLAGS = $0000ffff; +const INODE_NO_CACHE = $00010000; +const INODE_WAS_WRITTEN = $00020000; +const S_IFDIR = $00040000; // POSIX +const NO_TRANSACTION = $00040000; + +const DATASTREAM_DIRECT_BLOCKS = 16; // number of direct blocks in the dataStream + +const UBIXFS_MAGIC_1 = $A0A0A0A; +const UBIXFS_MAGIC_2 = $B0B0B0B; +const UBIXFS_MAGIC_3 = $C0C0C0C; +const UBIXFS_INODE_MAGIC = $3BBE0AD9; + +const UBIXFS_CLEAN = $434C454E; +const UBIXFS_DIRTY = $44495254; + +const SECTOR_SHIFT = 9; +const SECTOR_SIZE = 1 shl SECTOR_SHIFT; + +type + int8 = shortInt; + int16 = smallInt; + int32 = longInt; + + uInt8 = byte; + uInt16 = word; + uInt32 = dWord; +{ Common Integer array definitions } + +type + int8ArrayPtr = ^int8Array; + int8Array = array[ 0..0 ] of int8; + + int16ArrayPtr = ^int16Array; + int16Array = array[ 0..0 ] of int16; + + int32ArrayPtr = ^int32Array; + int32Array = array[ 0..0 ] of int32; + + { Common Unsigned Integer array definitions } + +type + uInt8ArrayPtr = ^uInt8Array; + uInt8Array = array[ 0..0 ] of uInt8; + + uInt16ArrayPtr= ^uInt16Array; + uInt16Array = array[ 0..0 ] of uInt16; + + uInt32ArrayPtr= ^uInt32Array; + uInt32Array = array[ 0..0 ] of uInt32; + +type + flock = object + lType:smallint; + lWhence:smallint; + lStart:integer; + lLen:integer; + lPID:integer; + end; // flock + +type + flock64 = object + lType:smallint; + lWhence:smallint; + lStart:int64; + lLen:int64; + lPID:integer; + end; // flock64 + +(* TBlockRun = packed record + AG : int32; + start : uInt16; + len : uInt16; + end; + + inodeAddr = TblockRun; + + uPtr = packed record + case integer of + 0: (i : int32); + 1: (u : uInt32); + 2: (s : single); + 3: (d : double); + 4: (p : pointer); + 5: (offset : int64); + 6: (iAddr : inodeAddr); + end; +*) + PDiskSuperBlock = ^TDiskSuperBlock; + TDiskSuperBlock = packed record + name : array[0..31] of char; + magic1 : int32; + + fsByteOrder : int32; + + // blockSize on disk + blockSize : uInt32; + + // number of bits needed to shift a block number to get a byte address + blockShift : uInt32; + + numBlocks : int64; + usedBlocks : int64; + + inodeSize : uInt32; + magic2 : uInt32; + + blocksPerAG : uInt32; + AGShift : uInt32; + numAGs : uInt32; + + flags : uInt32; + + logBlocks : TBlockRun; + logStart : int64; + logEnd : int64; + + magic3 : int32; + BAT : inodeAddr; + rootDir : inodeAddr; + indicies : inodeAddr; + + pad : array[0..371] of byte; + end; // TDiskSuperBlock + + PDataStream = ^TDataStream; + TDataStream = packed record + direct : array[0..DATASTREAM_DIRECT_BLOCKS-1] of TBlockRun; + maxDirect : int64; + indirect : TBlockRun; + maxIndirect : int64; + doubleIndirect: TBlockrun; + maxDIndirect : int64; + size : int64; + blockCount : uInt32; + end; // TDataStream + + PUbixfsInode = ^TUbixfsInode; + TUbixfsInode = packed record + magic : int32; + inodeNum : inodeAddr; + uid : uInt32; + gid : uInt32; + mode : int32; + flags : int32; + createTime : int64; + modifiedTime : int64; + parent : inodeAddr; + attributes : inodeAddr; + iType : uInt32; + inodeSize : uInt32; + data : uPtr; {this was the etc field in bfs} + blocks : TDataStream; + name : array[0..255] of char; + smallData : array[0..0] of byte; + end; // TUbixfsInode + + PCacheNode = ^TCacheNode; + TCacheNode = object + public + address : int64; + lastUsed : int64; + data : pointer; + dataSize : uInt32; + dirty : boolean; + locked : boolean; + constructor init(_address, _lastUsed:int64; _dataSize:uInt32); + destructor done; + end; // TCacheNode + + PVNode = ^TVNode; + TVNode = object + TID : uInt32; // Thread ID + iAddr : inodeAddr; + position : int64; + flags : uInt32; + end; // TVNode + + PVnid = ^TVnid; + TVnid = object + refCount : uInt32; + iAddr : inodeAddr; + mode : uInt32; + end; // TVnid + + PUbixFS = ^TUbixFS; + TUbixFS = object + private + dev : PDevice; + superBlock : PDiskSuperBlock; + blockCache : PbTree; + blockTime : PbTree; + vnids : PbTree; + vNodeQueue : PQueue; + fileDescriptors : ^array[0..0] of TVNode; + + maxCacheEntry : uInt32; + maxFDEntry : uInt32; + + procedure assert(x:boolean); + function allocBRun(blockRun:TBlockRun):TBlockRun; + function allocInode(parentIAddr:inodeAddr):PUbixfsInode; + function cacheBlock(blockAddr:TBlockRun):pointer; + function extendFile(inode:PUbixfsInode; newSize:int64):boolean; + function findBRun(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunDirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunDIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function freeUsedBlocks(blockRun:TBlockRun):integer; + function getFileSize(inode:PUbixfsInode):int64; + function getFileSpace(inode:PUbixfsInode):int64; + function iAddrToStr(iAddr:inodeAddr):string; + function isValidIAddr(iAddr:inodeAddr):boolean; + function loadBlock(blockAddr:TBlockRun):pointer; + + function markUsedBlocks(blockRun:TBlockRun):integer; + function nextAG(AG:uInt32):uInt32; + function pathToIAddr(path:PChar):inodeAddr; + + function readDataStream(iAddr:inodeAddr; position:int64; buf:pointer; length:uInt32):integer; + function saveBlock(blockAddr:TBlockRun; buf:pointer):boolean; + procedure setBlockRun(var blockRun:TBlockRun; AG:uInt32; start, len:uInt16); + procedure setIAddr(var inode:inodeAddr; AG:uInt32; start, len:uInt16); + function shrinkFile(inode:PUbixfsInode):boolean; + function strToIAddr(s:string):inodeAddr; + function writeDataStream(iAddr:inodeAddr; position:int64; buf:pointer; length:uInt32):integer; + public + constructor init(device:Pdevice); + procedure printSuperBlock; + procedure assign(var F:TFileRec; const _name:string); + procedure reset(var F:TFileRec; blkSize:uInt32); + procedure rewrite(var F:TFileRec; blkSize:uInt32); + procedure sync; + function syncBlock(cacheNode:PCacheNode):boolean; + {$IFDEF GRAPHICS} + procedure visualize; + {$ENDIF} + function vfs_fCreate(fileName:PChar):integer; + function vfs_fExist(path:PChar):integer; + function vfs_close(handle:integer):integer; + function vfs_format(device:PDevice; fsBlockSize:uInt32; quick:boolean):integer; + function vfs_init:integer; + function vfs_mkdir(path:PChar):integer; + function vfs_read(handle:integer; buffer:pointer; size:uInt32):integer; + function vfs_seek(handle:integer; newPosition:int64):integer; + function vfs_unlink(fileName:PChar):integer; + function vfs_write(handle:integer; buffer:pointer; size:uInt32):integer; + procedure examine; + destructor done; virtual; + end; // TUbixFS + +implementation + +uses strings, dos, math, lists; + +// IMPORTANT NOTE +// You cannot under any circumstance change NUM_EXTENTION_BLOCKS. This +// constant defines the multiple of blocks that a file will be extended +// by, *and* it determines how many blocks are used in each blockRun in +// the Double Indirect dataStream. +// NUM_EXTENSION_BLOCKS determines how much slack space is allocated for +// a file when it is extended. For example, a file with 0 bytes in it +// will take up 0 blocks for the data and 1 block for the inode (ignoring +// space required to hold the directory entry). When the first byte is +// written, extendFile() will allocate a multiple of NUM_EXTENSION_BLOCKS +// to accomodate the amount of data being written out. So, a 1 byte file will +// actually take up NUM_EXTENSION_BLOCKS (plus one for the inode) until the +// file is closed. Then the file will be trimmed and the slack space reclaimed. +// This value is also used in the double-indirect region of the dataStream +// to determine how many blocks each blockRun uses. + +// vv DO NOT CHANGE vv // +const NUM_EXTENSION_BLOCKS = 4; // <--- don't change this +// ^^ DO NOT CHANGE ^^ // + +const NUM_CACHE_ENTRIES = 8192; // default max entries in block cache +const NUM_FD_ENTRIES = 1024; // default max entries in the File Descriptor Table +const BAT_PAGE_SIZE = 256; // page size for the Block Allocation Tree + +type + PUbixBTreeVFS = ^TUbixBTreeVFS; + TUbixBTreeVFS = object(TBTreeVFS) + private + fs : PUbixFS; + iAddr : inodeAddr; + position : int64; + public + constructor init(_fs:PUbixFS); + function fClose:boolean; virtual; + function fCreate(const filename:string):boolean; virtual; + function fExist(const filename:string):boolean; virtual; + function fOpen(const filename:string):boolean; virtual; + function fRead(var buf; size:uInt32):boolean; virtual; + function fSeek(offset:uInt32):boolean; virtual; + function fWrite(var buf; size:uInt32):boolean; virtual; + destructor done; virtual; + end; // TUbixBTreeVFS + +function getRDTSCTime:int64; +var x:int64; +begin + asm + rdtsc + mov dword ptr x, eax + mov dword ptr x+4, edx + end; + result := x; +end; + +function null_file_proc conv arg_based (f: LongInt; buf: pointer; len: ULong; var act: ULong): LongInt; +begin + runerror(999); +end; + +// B^Tree compareKey BlockRun function +function compareKeyBlockRun(key1, key2:pointer):integer; +begin + result := 0; + if ((key1 = NIL) or (key2 = NIL)) then exit; + + if (TBlockRun(key1^).AG < TBlockRun(key2^).AG) then + result := -1 + else + if (TBlockRun(key1^).AG > TBlockRun(key2^).AG) then + result := 1 + else // AGs are the same + if (TBlockRun(key1^).start < TBlockRun(key2^).start) then + result := -1 + else + if (TBlockRun(key1^).start > TBlockRun(key2^).start) then + result := 1; // else they are identical +end; // compareKeyInt64 + +// B^Tree copyKey BlockRun function + +procedure copyKeyBlockRun(srcKey, destKey:pointer); +begin + if ((srcKey = NIL) or (destKey = NIL)) then exit; + TBlockRun(destKey^) := TBlockRun(srcKey^); +end; // copyKeyBlockRun; + +// B^Tree keySize BlockRun function + +function keySizeBlockRun(key:pointer):integer; +begin + if (key = NIL) then result := 0 else result := sizeof(TBlockRun); +end; // keySizeBlockRun + + +constructor TCacheNode.init; +begin + address := _address; + lastUsed := _lastUsed; + dataSize := _dataSize; + data := NIL; + if (dataSize <> 0) then GetMem(data, dataSize); + dirty := FALSE; + locked := FALSE; +end; // TCacheNode.init + +destructor TCacheNode.done; +begin + if (data <> NIL) and (dataSize <> 0) then FreeMem(data, dataSize); + address := 0; + lastUsed := 0; + data := NIL; + dataSize := 0; + dirty := FALSE; + locked := FALSE; +end; // TCacheNode.done + +constructor TUbixBTreeVFS.init; +begin +// writeln('TUbixBTreeVFS.init()'); + fs := _fs; + fillchar(iAddr, sizeof(iAddr), 0); + position := 0; +end; // TUbixBTreeVFS.init; + +function TUbixBTreeVFS.fClose; +var + tmpIAddr:inodeAddr; + tmpInode:PUbixfsInode; +begin + result := FALSE; + if (fs = NIL) then exit; + if not fs^.isValidIAddr(IAddr) then exit; + tmpInode := fs^.loadBlock(IAddr); + if (tmpInode = NIL) then exit; +/// fs^.shrinkFile(tmpInode); // shrink the directory + position := 0; + result := TRUE; +end; // TUbixBTreeVFS.fClose + +function TUbixBTreeVFS.fCreate; +begin + // I *think* that this code is only called for the + // B^Tree. Therefore, the file technically exists, but just + // has nothing in it when we call fCreate. + result := fOpen(filename); +end; // TUbixBTreeVFS.fCreate + +function TUbixBTreeVFS.fExist; +var + tmpIAddr:inodeAddr; + tmpInode:PUbixfsInode; +begin + result := FALSE; + assert(fs <> NIL); + if (fs = NIL) then exit; + assert(fs^.superBlock <> NIL); + if (fs^.superBlock = NIL) then exit; + tmpIAddr := fs^.strToIAddr(filename); + if not fs^.isValidIAddr(tmpIAddr) then exit; + + // If we've created an inode, but haven't written any data to it, then + // the B^Tree hasn't written out a superblock, so technically the file doesn't + // exist, and this is a really long run-on sentence. + tmpInode := fs^.loadBlock(tmpIAddr); + + // I'm not sure why, but the compiler cannot properly evaluate (tmpInode^.blocks.size <> 0), + // so we check both the high and low portions of the size to make sure it's not 0. + // result := (tmpInode <> NIL) and ((longLongRec(tmpInode^.blocks.size).lo <> 0) or (longLongRec(tmpInode^.blocks.size).hi <> 0)); + + // However, checking for 0 and then taking the complement of that works .. so we go that way. + result := ((tmpInode <> NIL) and (not (tmpInode^.blocks.size = 0))); +end; // TUbixBTreeVFS.fExist + +function TUbixBTreeVFS.fOpen; +begin + result := FALSE; + if (fs = NIL) then exit; + if (fs^.superBlock = NIL) then exit; + iAddr := fs^.strToIAddr(filename); + position := 0; + result := fs^.isValidIAddr(iAddr); +end; // TUbixBTreeVFS.fOpen + +function TUbixBTreeVFS.fRead; +begin + result := FALSE; + if (fs = NIL) then exit; +// with inode^.inodeNum do +// write('fRead(', AG, ':', start, ':', len, ', '); +// write(position, ', '); +// write('@buf, '); + + result := (fs^.readDataStream(iAddr, position, @buf, size) = 0); + position := position + int64(size); +end; // TUbixBTreeVFS.fRead + +function TUbixBTreeVFS.fSeek; +begin + // uhm.. if the desired offset is beyond the end of the file, + // should we not extend out the file? Or does that occur in writeDataStream ... + position := offset; + result := TRUE; +end; // TUbixBTreeVFS.fSeek + +function TUbixBTreeVFS.fWrite; +begin + result := FALSE; + if (fs = NIL) then exit; +// with inode^.inodeNum do +// write('fWrite(', AG, ':', start, ':', len, ', '); +// write(position, ', '); +// write('@buf, '); + result := (fs^.writeDataStream(iAddr, position, @buf, size) = 0); + position := position + int64(size); + //inc(position, size); +end; // TUbixBTreeVFS.fWrite; + +destructor TUbixBTreeVFS.done; +begin + // XXX: It's possible that we need to save the inode here since + // we possibly changed a directory + fillchar(iAddr, sizeof(iAddr), 0); + fs := NIL; // don't destruct it since we didn't allocate it +end; // TUbixBTreeVFS.done + +constructor TUbixFS.init; +begin + dev := device; + superBlock := NIL; + blockCache := NIL; + blockTime := NIL; + vnids := NIL; + vnodeQueue := NIL; + fileDescriptors := NIL; + maxCacheEntry := NUM_CACHE_ENTRIES; + maxFDEntry := NUM_FD_ENTRIES; +end; // TUbixFS.init + +procedure TUbixFS.assert(x:boolean); +begin + if not x then runerror(1000); +end; // TUbixFS.assert + +{$system} +procedure TUbixFS.assign; +begin + with f do begin + magic := @f; + state := [%file_assigned]; + name := _name; + rd_proc := null_file_proc; + wr_proc := null_file_proc; + check_magic(); + end; // with +end; // TUbixFS.assign + +{$system} +procedure TUbixFS.reset; +begin +// if IO_ApiRet <> 0 then IO_Error(IO_Apiret); +end; // TUbixFS.reset + +procedure TUbixFS.rewrite; +begin + +end; // TUbixFS.rewrite + +function TUbixFS.allocBRun; +var + FBL:PBTree; // free block list + u:uPtr; + tmpBlockRun:TBlockRun; + searchRec:BTreeSearchRec; + found:boolean; +begin + setBlockRun(result, 0, 0, 0); + // basic sanity checks +with blockRun do + writeln('allocBRun(', AG, ':', start, ':', len, ');'); + if (superBlock = NIL) or (blockRun.len = 0) then exit; + if not isValidIAddr(superBlock^.BAT) then exit; + + if (blockRun.AG > superBlock^.numAGs) then exit; + + if (superBlock^.usedBlocks + int64(blockRun.len) > superBlock^.numBlocks) then exit; // should probably give a louder warning + + new(FBL, open(iAddrToStr(superBlock^.BAT), new(PUbixBTreeVFS, init(@self)))); + if (FBL = NIL) then exit; + + FBL^.InstallUserFunctions(compareKeyBlockRun, copyKeyBlockRun, keySizeBlockRun); + setBlockRun(tmpBlockRun, blockRun.AG, 0, 0); + + fillchar(searchRec, sizeof(searchRec), 0); + + // tmpBlockRun points to the Allocation Group marker in the tree + if not FBL^.FindFirst(@tmpBlockRun, searchRec) then + begin + writeln('Could not find AG marker inside FBL'); + dispose(FBL, done); + exit; + end; + + found := FALSE; // have we found a free range large enough to accomodate our request? + repeat + if not FBL^.findNext(searchRec) then assert(FBL^.GetFirstKey(searchRec)); + // assert(searchRec.key <> NIL); // I don't think this can ever happen + with TBlockRun(searchRec.key^) do + if (len = 0) then + if (AG = blockRun.AG) then + break // We've wrapped completely around + else + continue; // look for another one + found := (TBlockRun(searchRec.key^).len >= blockRun.len); + until found; +(* + found = false; + do { + if (!FBL->FindNext(searchRec)) assert(FBL->GetFirstKey(searchRec)); + if (searchRec.key->len == 0) + if (searchRec.key->AG == blockRun.AG) break; else continue; + found = (searchRec.key->len >= blockRun.len); + } while (!found) +*) + // It's possible we didn't find a run of the size requested.. + if found then + begin + // No matter what, we're going to have to delete the old entry and insert a new one + + tmpBlockRun := TBlockRun(searchRec.key^); // make a copy + u := searchRec.value; + FBL^.Delete(@tmpBlockRun, u); // delete the old key + with tmpBlockRun do + begin + setBlockRun(result, AG, start, blockRun.len); // set the result to the associated blockrun + inc(start, blockRun.len); + dec(len, blockRun.len); + // I'm not sure what to put in the value field yet + if (len <> 0) then assert(FBL^.Insert(@tmpBlockRun, u)); // if there's any blocks left, re-insert them into the FBL + superBlock^.usedBlocks := superBlock^.usedBlocks + int64(blockRun.len); + end; // with + end; // if found + FBL^.FindClose(searchRec); // de-allocate the searchRec + FBL^.printList('batlist.txt'); + dispose(FBL, done); +end; // TUbixFS.allocBRun + +(* +function TUbixFS.allocBRun; +var + FBL:int8ArrayPtr; // Free Block List array pointer + FBLSize:uInt32; // Free Block List array pointer size + bitOffset:uInt32; + byteOffset:uInt32; + curBlock, baseBlock:uInt32; + freeCount:uInt32; +begin + // Notes: + // 2005-04-27 mji + // allocBRun() will allocate a blockRun that is near to the blockRun + // passed into the function. The AG and Start field of blockRun will be + // the starting point of where to look for free blocks. The len field + // holds how many contiguous blocks we need to allocate. + // We look through the BAT (Block Allocation Table) for a contiguous run that + // fits the requested length. The BAT is a bitfield (used blocks are an + // on (1) bit, free blocks are an off (0) bit) of used/unused logical + // blocks, where the logical block size was set when the device was formatted. + // This shouldn't be confused with the sector size on the device, which + // is always 512 bytes. Block sizes are usually 1k, but can be any power + // of 2 higher than that up to 8k. Since we only need to load one AG section + // out of the BAT at a time, we only allocate one block worth of memory. + // The disk is broken up into Allocation Groups, where each allocation group + // bitfield fits into a single block. This means that for a device with 1k + // blocks an AG will be 8192 blocks long. Small devices (such as a floppy) + // will only have 1 AG. + // To find a free run, we load a starting section of the BAT out of the + // BAT file. The BAT is technically another file on the volume, but unlike + // normal files the BAT inode is referenced inside the superblock, and cannot + // be touched by a normal user (if the volume is grown later, the BAT can be + // grown as well). + // Once the AG block list is loaded, we scan through looking for a contiguous + // set of free blocks that fits our length requirement. In the event this + // AG doesn't meet that requirement, we move to the next AG. When we hit the + // end of the volume, we go back to the beginning of the volume and continue + // scanning until either we find a free run or we end up back where we began. + // In the latter case, the disk would have to either be very fragmented or + // full. If we succeed, we mark the blocks as used and return the blockRun. + + // XXX To do: + // Might want to clear the blocks we allocated to avoid garbage from being present + // We also need to ensure that a run doesn't cross an AG. This is because some + // other pieces of code do not take into account the possibility of moving to the + // next AG (or wrapping around completely). This change will have to be + // coupled with a change in extendFile() to ask for decreasing block counts until one + // is found that fits inside an AG. + + setBlockRun(result, 0, 0, 0); + if (superBlock = NIL) then exit; + if (blockRun.AG > superBlock^.numAGs) then exit; + if (superBlock^.usedBlocks + int64(blockRun.len) > superBlock^.numBlocks) then exit; // should probably give a louder warning + + // we read in one block at a time + FBLSize := superBlock^.blockSize; + GetMem(FBL, FBLSize); + assert(FBL <> NIL); + fillchar(FBL^, FBLSize, 0); // better safe than sorry + assert(readDataStream(superBlock^.BAT, (blockRun.AG shl superblock^.AGShift) shr 3, FBL, FBLSize) = 0); + byteOffset := blockRun.start shr 3; + bitOffset := 7-(blockRun.start and 7); + baseBlock := (blockRun.AG shl superBlock^.AGShift) + blockRun.start; + curBlock := baseBlock; + freeCount := 0; + // an optimization would be to check to see if the byte is -1, thus skipping 8 at a time + // This will need to be implemented (but can be later) + repeat + + inc(curBlock); + + if (FBL^[byteOffset] and (1 shl bitOffset) = 0) then + inc(freeCount) + else + freeCount := 0; + + if (freeCount = blockRun.len) then break; // did we meet our length requirement? + + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + + if (byteOffset = superBlock^.blockSize) then + begin + // We scanned through one Allocation Group of blocks. Move to the next one + // XXX + // We have a few options for crossing an AG. + // 1) Reset free count to 0. If there's not a run long enough to handle the requested + // blockRun length, throw it back to extendFile() and let it break up the run into smaller chunks + // 2) Return the longest run we've encountered so far. This would make extendFile() loop until + // all the block requests were accomodated. + // 3) Rewrite this function to use some sort of B^Tree in a similar way to how XFS + // does it. (Not for the faint of heart, and might not be possible). + inc(blockRun.AG); + + // it's possible that we're wrapping back around to the beginning. + if (blockRun.AG = superBlock^.numAGs) then + begin + // We hit the end of the volume and wrapped back around + blockRun.AG := 0; // Back to the beginning AG + curBlock := 0; // curBlock is from the base of the volume + freeCount := 0; // reset this to 0 + end; + // read in the FBL for the AG we're currently on + assert(readDataStream(superBlock^.BAT, (blockRun.AG shl superblock^.AGShift) shr 3, FBL, FBLSize) = 0); + byteOffset := 0; // first byte in this block + bitOffset := 7; // first bit of the first byte + end; // if byteOffset = superBlock^.blocksPerAG + + until curBlock = baseBlock; // repeat until we end up back where we started + + if (freeCount = blockRun.len) then // we found a run + begin + blockRun.AG := (curBlock-blockRun.len) shr superBlock^.AGShift; + blockRun.start := (curBlock-blockRun.len) and (superBlock^.blocksPerAG-1); + markUsedBlocks(blockRun); + result := blockRun; + end + else writeln('Failure in allocBRun()'); + FreeMem(FBL, FBLSize); + +end; // TUbixFS.allocBRun +*) +function TUbixFS.allocInode; +var + inode:PUbixfsInode; + blockRun:TBlockRun; + +begin + // XXX ToDo: + // need to fill in the rest of the fields in the inode record + + // Notes: + // 2005-04-27 mji + // Inodes take up exactly one logical block on the device. They + // are usually in the same AG as their parent directory. Allocating + // one involves getting some memory for it and finding a free block + // to hold it on the device. + + result := NIL; + if (superBlock = NIL) then exit; + assert(parentIAddr.AG < superBlock^.numAGs); + if (parentIAddr.AG >= superBlock^.numAGs) then exit; // parent addr is outside out block count? + // ask for a single block somewhere around the beginning of the requested AG + setBlockRun(blockRun, parentIAddr.AG, 0, 1); + blockRun := allocBRun(blockRun); + assert(blockRun.len <> 0); + if (blockRun.len = 0) then exit; // this would be bad + // Normally we'd allocate memory for the inode. Instead we let loadBlock() do that + // for us. + //inode := loadBlock(blockRun); // candidate for cacheBlock() + inode := cacheBlock(blockRun); + + assert(inode <> NIL); + if (inode = NIL) then exit; + fillchar(inode^, superBlock^.inodeSize, 0); + + with inode^ do + begin + magic := UBIXFS_INODE_MAGIC; + + // inodes point to themselves + inodeNum := blockRun; + + uid := 0; // this doesn't mean much here + gid := 0; + // mode := ?? + flags := INODE_NO_FLAG; + // createTime := + // modifiedTime := + + parent := parentIAddr; + setIAddr(attributes, 0, 0, 0); + inodeSize := superBlock^.inodeSize; + // data := 0; + // there's no data yet in the file, so don't + // set blocks to anything (cleared above) + blocks.blockCount := 0; + + end; // with + result := inode; +end; // TUbixFS.allocInode + +function TUbixFS.cacheBlock; +var + blockAddress:int64; + cacheNode:PCacheNode; + u:uPtr; + searchRec:BTreeSearchRec; +begin + result := NIL; // failure by default + + if (dev = NIL) or (superBLock = NIL) or (blockcache = NIL) then exit; + + with blockAddr, superBlock^ do + blockAddress := (AG shl AGShift) + start; + + fillchar(u, sizeof(u), 0); // jut to be safe + cacheNode := NIL; + + if (blockCache^.Find(@blockAddress, u)) then + begin + // block was in cache + assert(u.p <> NIL); + cacheNode := u.p; + assert(cacheNode^.data <> NIL); + fillchar(cacheNode^.data^, superBlock^.blockSize, 0); + cacheNode^.dirty := TRUE; + // Now we need to update the blockTime. We do this by first deleting + // the cacheNode out of the blockTime cache. + blockTime^.Delete(@cacheNode^.lastUsed, u); // Delete old one + cacheNode^.lastUsed := getRDTSCTime(); // Get the new timestamp + blockTime^.Insert(@cacheNode^.lastUsed, u); // Insert with new time + end + else + begin + // Requested block isn't in cache + + // Check to make sure cache isn't full + + if (blockCache^.getKeyCount() = maxCacheEntry) then + begin + blockTime^.getFirstKey(searchRec); // least recently used block + cacheNode := searchRec.value.p; // get the pointer from the value + assert(syncBlock(cacheNode)); +(* + cacheNode := searchRec.value.p; // get the pointer from the value + assert(cacheNode <> NIL); + // I suppose we could write a syncBlock() function, since this + // code is also duplicated in sync(). + if (cacheNode^.dirty) then + begin + with cacheNode^ do + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + cacheNode^.dirty := FALSE; + end; // if dirty *) + + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + blockTime^.Delete(@cacheNode^.lastUsed, u); + // We've deleted the cacheNode out of both the blockCache and + // the blockTime trees. We're going to re-use the cacheNode. + cacheNode^.address := blockAddress; + cacheNode^.lastUsed := getRDTSCTime(); + // No need to update data, dataSize, or dirty. We re-use the first two, + // and dirty will be set to FALSE above if necessary. Might need to + // reset locked if we're using that. + end + else + new(cacheNode, init(blockAddress, getRDTSCTime(), superBlock^.blockSize)); + + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + u.p := cacheNode; + if not (blockCache^.Insert(@blockAddress, u)) then + writeln('Error inserting ', blockAddress, ' into blockCache in cacheBlock()'); + if not (blockTime^.Insert(@cacheNode^.lastUsed, u)) then + writeln('Error inserting into blockTime'); + end; + + if (cacheNode <> NIL) then + begin + fillchar(cacheNode^.data^, superBlock^.blockSize, 0); + cacheNode^.dirty := TRUE; // mark this block as dirty + result := cacheNode^.data; // return the data pointer + end; + +end; // TUbixFS.cacheBlock + +function TUbixFS.extendFile; + + function appendToDIndirect(newBlockRun:TBlockRun):boolean; + var + extents, indirectExtents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + extentsIndex, indirectExtentsIndex:uInt32; + extentsBlockIndex, indirectExtentsBlockIndex:uInt32; + extentsUpperBound:uInt32; + tmpBlockRun:TBlockRun; + begin +writeln('in appendToDIndirect()'); + // Many apologies for the long variable names. This section is relatively + // complicated, and it wouldn't help to have cryptic variables. + result := FALSE; // failure by default + if (newBlockRun.len mod NUM_EXTENSION_BLOCKS <> 0) then + begin + writeln('Double indirect block run is not a multiple of NUM_EXTENSION_BLOCKS'); + exit; + end; + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun)) - 1; + + if not isValidIAddr(inode^.blocks.doubleIndirect) then + begin + // allocate a double indirect block + tmpBlockRun := inode^.inodeNum; // allocate a block near the parent inode + tmpBlockRun.len := NUM_EXTENSION_BLOCKS; + inode^.blocks.doubleIndirect := allocBRun(tmpBlockRun); + + if not isValidIAddr(inode^.blocks.doubleIndirect) then + begin + writeln('Failed to allocate double indirect block'); + exit + end; + tmpBlockRun := inode^.blocks.doubleIndirect; + for extentsBlockIndex := low(extents) to high(extents) do + begin + extents[extentsBlockIndex] := cacheBlock(tmpBlockRun); + //extents[extentsBlockIndex] := loadBlock(tmpBlockRun); // good candidate for cacheBlock() + //fillchar(extents[extentsBlockIndex]^, superBlock^.blockSize, 0); + //assert(saveBlock(tmpBlockRun, extents[extentsBlockIndex])); + inc(tmpBlockRun.len); + end; // for extentsBlockIndex + + // We have a freshly allocated double indirect block. Now we need an indirect block + // tmpBlockRun := inode^.blocks.doubleIndirect; // allocate an indirect block near the dindirect block + // tmpBlockRun.len := NUM_EXTENSION_BLOCKS; // theoretically, it is already + // extents[0]^[0] := allocBRun(tmpBlockRun); + extents[0]^[0] := allocBRun(inode^.blocks.doubleIndirect); + if not isValidIAddr(extents[0]^[0]) then + begin + writeln('Failed to allocate indirect block beneath a double indirect block'); + exit + end; + // save out the doubleIndirect block + inode^.blocks.maxDIndirect := inode^.blocks.maxIndirect; + //assert(saveBlock(inode^.blocks.doubleIndirect, extents)); + // This is a little misleading. Since saveBlock() formulates the + // address based on tbe base-block of the blockRun, this will only save + // out the first block. Secondly, we may not even need to do this since + // we tagged them for saving above. + assert(saveBlock(inode^.blocks.doubleIndirect, extents[0])); + + tmpBlockRun := extents[0]^[0]; + for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + begin + indirectExtents[indirectExtentsBlockIndex] := cacheBlock(tmpBlockRun); // good candidate for cacheBlock() + (* indirectExtents[indirectExtentsBlockIndex] := loadBlock(tmpBlockRun); // good candidate for cacheBlock() + fillchar(indirectExtents[indirectExtentsBlockIndex]^, superBlock^.blockSize, 0); + assert(saveBlock(tmpBlockRun, indirectExtents[indirectExtentsBlockIndex])); *) + inc(tmpBlockRun.start); + end; // for indirectExtentsBlockIndex + + // Because we have a freshly allocated double indirect block, we have no entries + // in it yet. This means that our DIndirect index is 0, and our indirect index is also 0. + extentsIndex := 0; + extentsBlockIndex := 0; + indirectExtentsIndex := 0; + indirectExtentsBlockIndex := 0; + end + else + begin + // double indirect block exists. Load it + tmpBlockRun := inode^.blocks.doubleIndirect; + for extentsBlockIndex := low(extents) to high(extents) do + begin + extents[extentsBlockindex] := loadBlock(tmpBlockRun); + assert(extents[extentsBlockindex] <> NIL); + inc(tmpBlockRun.start); + end; // for extentsBlockIndex + + // Now scan through (backwards) to find the last entry. + for extentsBlockIndex := high(extents) downto low(extents) do + if (extents[extentsBlockIndex]^[0].len <> 0) then break; + + for extentsIndex := extentsUpperBound downto 0 do + if (extents[extentsBlockIndex]^[extentsIndex].len <> 0) then break; + // [extentsBlockindex],[extentsIndex] points to the entry which holds the indirect block with the last entry. + // load that one + tmpBlockRun := extents[extentsBlockindex]^[extentsIndex]; + for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + begin + indirectExtents[indirectExtentsBlockIndex] := loadBlock(tmpBlockRun); + assert(indirectExtents[indirectExtentsBlockIndex] <> NIL); + inc(tmpBlockRun.start); + end; // for indirectExtentsBlocksIndex + + // first check the last entry + if (indirectExtents[high(indirectExtents)]^[extentsUpperBound].len <> 0) then + begin + // this indirect block is full. Allocate a new one + if ((extentsIndex = extentsUpperBound) and (extentsBlockIndex = high(extents))) then + begin + // I might as well halt at this point because the user is totally hosed. + writeln('Egad! The double indirect block section is full. Can not write any more to this file!'); + exit; + end; // if doomed + tmpBlockRun := extents[extentsBlockIndex]^[extentsIndex]; // address of the aforementioned full indirect block + // tmpBlockRun.len := NUM_EXTENSION_BLOCKS; // should be already + if (extentsIndex = extentsUpperBound) then + begin + extentsIndex := 0; + inc(extentsBlockIndex); // checked above to see if this was full + end + else + inc(extentsIndex); + + extents[extentsBlockIndex]^[extentsIndex] := allocBRun(tmpBlockRun); + tmpBlockRun := extents[extentsBlockIndex]^[extentsIndex]; // grab a copy of the brun we just made + assert(isValidIAddr(tmpBlockRun)); // better safe than sorry + for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + begin + indirectExtents[indirectExtentsBlockIndex] := cacheBlock(tmpBlockRun); // good candidate for cacheBlock() + (* indirectExtents[indirectExtentsBlockIndex] := loadBlock(tmpBlockRun); // good candidate for cacheBlock() + fillchar(indirectExtents[indirectExtentsBlockIndex]^, superBlock^.blockSize, 0); + assert(saveBlock(tmpBlockRun, indirectExtents[indirectExtentsBlockIndex])); *) + inc(tmpBlockRun.start); + end; + tmpBlockRun := inode^.blocks.doubleIndirect; + inc(tmpBlockRun.start, extentsBlockIndex); + saveBlock(tmpBlockRun, extents[extentsBlockIndex]); // save, since we added an indirect block to it + indirectExtentsIndex := 0; + indirectExtentsBlockIndex := 0; + end + else + begin + // now scan forward + indirectExtentsBlockIndex := 0; + indirectExtentsIndex := 0; + while (indirectExtents[indirectExtentsBlockIndex]^[indirectExtentsIndex].len <> 0) do + begin + if (indirectExtentsIndex = extentsUpperBound) then + begin + indirectExtentsIndex := 0; + if (indirectExtentsBlockIndex = high(indirectExtents)) then break else inc(indirectExtentsBlockIndex); + end + else + inc(indirectExtentsIndex); + end; // while +(* for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + for indirectExtentsIndex := 0 to extentsUpperBound do + if (indirectExtents[indirectExtentsBlockIndex]^[indirectExtentsIndex].len = 0) then break; *) +writeln('[1] indirectExtentsIndex: ', indirectExtentsIndex); +writeln('[1] indirectExtentsBlockIndex: ', indirectExtentsBlockIndex); + + end; + // [extentsBlockIndex],[extentsIndex] points to which indirect block we're on + // [indirectExtentsBlockIndex],[indirectExtentsIndex] points to the first free entry in that indirect block + end; + + while (newBlockRun.len <> 0) do + begin + tmpBlockRun := newBlockRun; + tmpBlockRun.len := NUM_EXTENSION_BLOCKS; +writeln('indirectExtentsIndex: ', indirectExtentsIndex); +writeln('indirectExtentsBlockIndex: ', indirectExtentsBlockIndex); + indirectExtents[indirectExtentsBlockIndex]^[indirectExtentsIndex] := tmpBlockRun; + with newBlockRun do + begin + dec(len, NUM_EXTENSION_BLOCKS); + if ((uInt32(start) + NUM_EXTENSION_BLOCKS) > high(start)) then inc(AG); + inc(start, NUM_EXTENSION_BLOCKS); // Warning! Relies on integer wrap-around (start is 16-bit) + end; // with + + if (indirectExtentsIndex = extentsUpperBound) then + begin + // this indirect block is full. + // Save the current one + assert(saveBlock(extents[extentsBlockIndex]^[extentsIndex], indirectExtents[indirectExtentsBlockIndex])); + if (indirectExtentsBlockIndex = high(indirectExtents)) then + begin + // Allocate a new one + if ((extentsIndex = extentsUpperBound) and (extentsBlockIndex = high(extents))) then + begin + // I might as well halt at this point because the user is totally hosed. + writeln('Egad! The double indirect block section is full. Can not write any more to this file!'); + exit; + end; // if doomed + tmpBlockRun := extents[extentsBlockIndex]^[extentsIndex]; // snag the previous location + // update which index in the double indirect block we're using + if (extentsIndex = extentsUpperBound) then + begin + inc(extentsBlockIndex); + extentsIndex := 0; + end + else + inc(extentsIndex); + + extents[extentsBlockIndex]^[extentsIndex] := allocBRun(tmpBlockRun); // allocate an indirect block near the previous one + // make a copy of the indirect block we just allocated + tmpBlockRun := extents[extentsBlockIndex]^[extentsIndex]; + assert(isValidIAddr(tmpBlockRun)); // just to be sure + for indirectExtentsBlockIndex := low(indirectExtents) to high(indirectExtents) do + begin + indirectExtents[indirectExtentsBlockIndex] := cacheBlock(tmpBlockRun); // candidate for cacheBlock() + (* indirectExtents[indirectExtentsBlockIndex] := loadBlock(tmpBlockRun); // candidate for cacheBlock() + assert(indirectExtents[indirectExtentsBlockIndex] <> NIL); + fillchar(indirectExtents[indirectExtentsBlockIndex]^, superBlock^.blockSize, 0); + assert(saveBlock(tmpBlockRun, indirectExtents[indirectExtentsBlockIndex])); *) + inc(tmpBlockRun.start); + end; // for indirectExtentsBlockIndex + indirectExtentsIndex := 0; + indirectExtentsBlockIndex := 0; + // Here we need to save the extents block we just changed --> + tmpBlockRun := inode^.blocks.doubleIndirect; + inc(tmpBlockRun.start, extentsBlockIndex); + assert(saveBlock(tmpBlockRun, extents[extentsBlockIndex])); + // <--- + end + else + begin + // We just need to pop over to the next section of the indirect block. + // At this point I really want to beat the BeOS guy who decided to make these strucutres + // span multiple disk blocks. They said [in the book] that they didn't want to do that because + // it complicated things. Well, gee, no kidding. + inc(indirectExtentsBlockIndex); + indirectExtentsIndex := 0; + end; + end + else + inc(indirectExtentsIndex); + // update the size the maxDoubleIndirect blocks reference + inode^.blocks.maxDIndirect := inode^.blocks.maxDIndirect + (NUM_EXTENSION_BLOCKS shl superBlock^.blockShift); + end; // while + // XXX ?-> saveBlock(extents^[extentsIndex], indirectExtents); + end; // local function appendToDIndirect() + + function appendToIndirect(newBlockRun:TBlockRun):boolean; + var + extents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + extentsUpperBound, blockIndex, index:uInt32; + tmpBlockRun:TBlockRun; + begin + result := FALSE; // failure by default + // The indirect block is NUM_EXTENSION_BLOCKS long + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun))-1; + + for blockIndex := 0 to NUM_EXTENSION_BLOCKS-1 do + extents[blockIndex] := NIL; // NIL out the array + + if not isValidIAddr(inode^.blocks.indirect) then + begin + // allocate an indirect block + tmpBlockRun := inode^.inodeNum; // alloate near the parent inode + tmpBlockRun.len := NUM_EXTENSION_BLOCKS; // indirect block is NUM_EXTENSION_BLOCKS long + inode^.blocks.indirect := allocBRun(tmpBlockRun); // Try to allocate it + if not isValidIAddr(inode^.blocks.indirect) then + begin + writeln('Failed to allocate indirect block'); + exit + end; + tmpBlockRun := inode^.blocks.indirect; + for blockIndex := 0 to NUM_EXTENSION_BLOCKS-1 do + begin + extents[blockIndex] := cacheBlock(tmpBlockRun); // candidate for cacheBlock() +(* extents[blockIndex] := loadBlock(tmpBlockRun); // candidate for cacheBlock() + fillchar(extents[blockIndex]^, superBlock^.blockSize, 0); + saveBlock(tmpBlockRun, extents[blockIndex]); *) + inc(tmpBlockRun.start); // XXX hopefully this won't wrap around. + end; // for index +// extents := loadBlock(inode^.blocks.indirect); + + inode^.blocks.maxIndirect := inode^.blocks.maxDirect; + end + else + begin + // the indirect block exists, so read it in + tmpBlockRun := inode^.blocks.indirect; + for blockIndex := low(extents) to high(extents) do + begin + extents[blockIndex] := loadBlock(tmpBlockRun); + assert(extents[blockIndex] <> NIL); + inc(tmpBlockRun.start); + end; // for i + end; + // extents[] has the indirect block in it + // We first check to make sure that we're not going to be moving + // to the next section of the dataStream + if (extents[high(extents)]^[extentsUpperBound].len <> 0) then + result := appendToDIndirect(newBlockRun) // promote the newBlockRun to the DIndirect section + else + begin + // scan through for the first free slot + blockIndex := 0; + index := 0; + while (extents[blockIndex]^[index].len <> 0) do + begin + if (index = extentsUpperBound) then + begin + index := 0; + if (blockIndex = high(extents)) then break else inc(blockIndex); + end + else + inc(index); + end; // while +(* for blockIndex := 0 to NUM_EXTENSION_BLOCKS-1 do + for index := 0 to extentsUpperBound do + if extents[blockIndex]^[index].len = 0 then break; *) + extents[blockIndex]^[index] := newBlockRun; // index points to the first free slot. Fill it in with the newBlockRun + tmpBlockRun := inode^.blocks.indirect; + inc(tmpBlockRun.start, blockIndex); // hopefully this won't wrap around + saveBlock(tmpBlockRun, extents[blockIndex]); // save out the indirect block. Could check for failure here + // update the size the maxDoubleIndirect blocks reference + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; // success + end; + end; // local function appendToIndirect() + + function appendToDirect(newBlockRun:TBlockRun):boolean; + var index:uInt32; + begin + with newBlockRun do + writeln('in appendToDirect(); blockRun = ', ag, ':', start, ':', len); + result := FALSE; + with inode^, blocks do + begin + if (direct[high(direct)].len <> 0) then + result := appendToIndirect(newBlockRun) + else + begin + for index := low(direct) to high(direct) do + if (direct[index].len = 0) then break; + direct[index] := newBlockRun; + maxDirect := maxDirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end; // else + end; // with + end; // local function appendToDirect + + function appendToDataStream(newBlockRun:TBlockRun):boolean; + begin + if not(inode^.blocks.maxDIndirect = 0) then result := appendToDIndirect(newBlockRun) + else if not(inode^.blocks.maxIndirect = 0) then result := appendToIndirect(newBlockRun) + else result := appendToDirect(newBlockRun); + end; // local function appendToDataStream + + function continueIndirect(newBlockRun:TBlockRun):boolean; + var + extents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + extentsUpperBound, blockIndex, index:uInt32; + tmpBlockRun:TBlockRun; + begin + result := FALSE; + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun))-1; + + tmpBlockRun := inode^.blocks.indirect; + for blockIndex := low(extents) to high(extents) do + begin + extents[blockIndex] := loadBlock(tmpBlockRun); + assert(extents[blockIndex] <> NIL); + inc(tmpBlockRun.start); // hopefully this won't wrap + end; + + for blockIndex := high(extents) downto low(extents) do + if (extents[blockIndex]^[0].len <> 0) then break; + + for index := extentsUpperBound downto 0 do + if (extents[blockIndex]^[index].len <> 0) then break; + + assert(extents[blockIndex]^[index].len <> 0); // just to be sure + // extents[blockIndex]^[index] has the last entry + if (uInt32(extents[blockIndex]^[index].len) + newBlockRun.len < 65535) then + begin + inc(extents[blockIndex]^[index].len, newBlockRun.len); + tmpBlockRun := inode^.blocks.indirect; + inc(tmpBlockRun.start, blockIndex); + saveBlock(tmpBlockRun, extents[blockIndex]); + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end + else // oops, we overflowed the length + begin + // need to check to see if we were the last indirect entry. If we were + // then we need to allocate a double indirect block and store it in there + if (index = extentsUpperBound) then + begin + if (blockIndex = high(extents)) then + result := appendToDIndirect(newBlockRun) + else + begin + inc(blockIndex); + extents[blockIndex]^[0] := newBlockRun; + tmpBlockRun := inode^.blocks.indirect; + inc(tmpBlockRun.start, blockIndex); + saveBlock(tmpBlockRun, extents[blockIndex]); + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end + end + else + begin + extents[blockIndex]^[index+1] := newBlockRun; + tmpBlockRun := inode^.blocks.indirect; + inc(tmpBlockRun.start, blockIndex); + saveBlock(tmpBlockRun, extents[blockIndex]); + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end; // else + end; // else + + end; // local function continueIndirect + + function continueDirect(newBlockRun:TBlockRun):boolean; + var + index:uInt32; + begin + result := FALSE; + with inode^, blocks do + begin + for index := high(direct) downto low(direct) do + if (direct[index].len <> 0) then break; + + assert(direct[index].len <> 0); // just to be sure + // make sure we don't overflow the length + if (uInt32(direct[index].len) + newBlockRun.len < 65535) then + begin + inc(direct[index].len, newBlockRun.len); // update the last entry + maxDirect := maxDirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end + else // oops, we overflowed the length + begin + // need to check to see if we were the last direct entry. If we were + // then we need to allocate an indirect block and store it in there + if (index = high(direct)) then + result := appendToIndirect(newBlockRun) + else + begin + direct[index+1] := newBlockRun; // store it in the next available entry + maxDirect := maxDirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end; + end; // else + end; // with + end; // local function continueDirect + + function continueDataStream(newBlockRun:TBlockRun):boolean; + begin + // The extended blockRun continues a previous run. + + // When extending the file we request an allocation of more blocks + // near to the last blockRun of the file. If the new blocks + // are contiguous to the last blockRun of the file + // then we can simply extend that blockRun. This not only + // prevents fragmentation when possible, it also reduces the + // number of actual blockRuns needed to store the file. + if (inode^.blocks.maxDIndirect > 0) then result := appendToDIndirect(newBlockRun) + else if (inode^.blocks.maxIndirect > 0) then result := continueIndirect(newBlockRun) + else result := continueDirect(newBlockRun); + end; // local function continueDataStream + +var + blockRun, exBlockRun:TBlockRun; + blocksToAllocate:uInt32; + +begin // extendFile() + // XXX ToDo: + // Need to verify support for indirect and double-indirect blocks + + // Notes: + // 2005-04-27 mji + // This function is called by writeDataStream() when we're about to write + // past the end of the file. When extending out a file, it is almost never + // extended by the amount written. Instead it is at least 1 block, and usually + // more (in our case: 4, as mentioned below). Currently we pass in the number + // of blocks to extend the file by. This is problematic for a few reasons. + // The first is that the code to figure out how much to extend the file by + // would have to be in writeDataStream(). That's not necessarily a problem, + // except that the amount to be extended by depends on many factors, some + // including whether we're writing out more than the default small block + // extension size (currently is 4) or if we're in double indirect blocks, + // which extends out by NUM_DINDIRECT_BLOCKS (also currently 4). + // The second problem is that we save the inode here -- and also in + // writeDataStream() -- because the new size of the file is updated in wDS() + // instead of here. + // The third problem is when you seek past the end of the file and then + // write. Space should be allocated for all the space between the old + // EOF and the new write position. This is common behaviour for most + // filesystems, and should probably be emulated here as well. + // The proper solution is to pass in the new size and let this function + // work out exactly how much space on disk it is really going to take. + + result := FALSE; // failure by default + // basic sanity checks + if (inode = NIL) or (superBlock = NIL) then exit; + + // if the file size is 0 then this is the first extension. + if (inode^.blocks.size = 0) then // first extension + begin + // A file's data is stored in the next allocation group from it's parent + // inode. This will help distribute the data across the disk. + + // Find out how many blocks the new size will take up. Do this by rounding + // up the size to the nearest block. Then we round that up to the nearest + // NUM_DIRECT_BLOCKS multiple. + blocksToAllocate := uInt32(((newSize+(superBlock^.blockSize-1)) shr superBlock^.blockShift)); + blocksToAllocate := (blocksToAllocate + (NUM_EXTENSION_BLOCKS-1)) and not (NUM_EXTENSION_BLOCKS-1); + // Allocate directory info in the same AG as the inode + if (inode^.mode and S_IFDIR <> 0) then + setBlockRun(exBlockRun, inode^.inodeNum.AG, 0, blocksToAllocate) + else + setBlockRun(exBlockRun, nextAG(inode^.inodeNum.AG), 0, blocksToAllocate); + exBlockRun := allocBRun(exBlockRun); // try to allocate a blockrun + assert(exBlockRun.len <> 0); + if (exBlockRun.len = 0) then exit; // erp. Couldn't extend the file + inode^.blocks.direct[0] := exBlockRun; // Set the first direct run + // The space allocated for the file can and may be different than the + // actual size. Set the maxDirect to the size (in bytes) of how much space + // we allocated + inode^.blocks.maxDirect := blocksToAllocate shl superBlock^.blockShift; + end // size = 0 + else + begin + // the size of the file wasn't 0 (handled above). We need to extend the + // file out. Find where the last blocks of the file are + + // Note: + // 2005-04-28 mji + // We're going to have a potential problem here. When we check to see how many blocks we + // want to extend by, we don't know for sure if we're actually going to end up in the + // dataStream section we were assuming. For example, we may have no more room in the + // indirect block section of dataStream and thus will have to store the entry in the double + // indirect area. If the constants for each section are different, then we may be requesting the + // wrong amount. This is *only* a problem if any of the constants are different. Therefore + // the solution is to pick some arbitrary extension amount (probably 4 or 8 blocks) and stick + // with that. If we pick the amount that will end up in the double indirect area (currently 4 + // but I'm considering 8) then we can easily break apart our blockRun if we're writing it + // to the double indirect section. + // IMPORTANT NOTE + // Once this value is chosen it will be set in stone since we will break everything by + // changing it. Math done for retrieving the blockRun from the double indirect region will + // rely on this value, and it can never be changed. BeFS uses 4 blocks for the double + // indirect section, and that seems fine with me. The largest file using contiguous blockRuns + // will be 38GB. I am, however, pondering 8 blocks instead. Remember, extensions are done + // as a multiple of this value. + // 2005-04-28 mji + // I decided to use a single constant, NUM_EXTENSION_BLOCKS, and it is set to 4. + blocksToAllocate := uInt32((newSize+(superBlock^.blockSize-1)) shr superBlock^.blockShift); + // blocksToAllocate is now rounded up to the nearest block of how much space we need. + blocksToAllocate := (blocksToAllocate + (NUM_EXTENSION_BLOCKS-1)) and not (NUM_EXTENSION_BLOCKS-1); + // blocksToAllocate is now rounded up to the nearest multiple of NUM_EXTENSION_BLOCKS + // now subtract off how many blocks we already have + dec(blocksToAllocate, inode^.blocks.blockCount); + + // XXX + // I don't think this is right. We need to find the last block returned by + // getFileSpace(), not the size. Something to ponder/look into/test + //blockRun := findBRun(@inode^.blocks, inode^.blocks.size shr superBlock^.blockShift); + assert(inode^.blocks.blockCount <> 0); // if blockCount is 0, we're in big trouble + blockRun := findBRun(@inode^.blocks, inode^.blocks.blockCount-1); + assert(blockRun.len <> 0); + if (blockRun.len = 0) then exit; // This would be odd + exBlockRun := blockRun; // copy the last run to exBlockRun + exBlockRun.len := blocksToAllocate; // set the requested number of blocks + exBlockRun := allocBRun(exBlockRun); // allocate a block run near the previous one + assert(exBlockRun.len <> 0); + if (exBlockRun.len = 0) then exit; // should probably yell louder about this + + // blockRun points to the last blockRun structure associated with this inode + if (exBlockRun.start = blockRun.start+1) and (exBlockRun.AG = blockRun.AG) then + continueDataStream(exBlockRun) + else + appendToDataStream(exBlockRun); + end; // else size <> 0 + inode^.blocks.size := newSize; // Now we update the size here and not in writeDataStream() + inc(inode^.blocks.blockCount, blocksToAllocate); // update the blockCount in the inode dataStream + // no matter where we stored the extended blockRun, we have updated the inode + + assert(saveBlock(inode^.inodeNum, inode)); // 0 result means success + result := TRUE; // no error +end; // TUbixFS.extendFile + +function TUbixFS.findBRun; +var + position:int64; +begin + setBlockRun(result, 0, 0, 0); + if (dataStream = NIL) then exit; + if (blockNum >= dataStream^.blockCount) then + begin + writeln('Requested block outside the block count in the dataStream'); + exit + end; + + position := int64(blockNum) shl superBlock^.blockShift; + if (position < dataStream^.maxDirect) then + result := findBRunDirect(dataStream, blockNum) + else if (position < dataStream^.maxIndirect) then + result := findBRunIndirect(dataStream, blockNum) + else if (position < dataStream^.maxDIndirect) then + result := findBRunDIndirect(dataStream, blockNum) + else writeln('Block position is not mapped by dataStream'); +end; // TUbixFS.findBRun + +function TUbixFS.findBRunDirect; +var + sum, i, offset:uInt32; +begin + // set the result to a nil blockrun + setBlockRun(result, 0, 0, 0); + + sum := 0; + with dataStream^ do + for i := low(direct) to high(direct) do + with direct[i] do + begin + if (blockNum < sum + len) then // found it + begin + offset := blockNum - sum; + setBlockRun(result, AG, start + offset, len - offset); + exit + end; + inc(sum, len); + end; // for/with +end; // TUbixFS.findBRunDirect + +function TUbixFS.findBRunIndirect; +var + extents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + sum, offset, blockIndex, index, extentsUpperBound:uInt32; + tmpBlockRun:TBlockRun; +begin + // NOTE: Basic error checking on whether superblock <> NIL isn't done here. + setBlockRun(result, 0, 0, 0); + + // load the indirect block + tmpBlockRun := dataStream^.indirect; + for blockIndex := low(extents) to high(extents) do + begin + extents[blockIndex] := loadBlock(tmpBlockRun); + assert(extents[blockIndex] <> NIL); + inc(tmpBlockRun.start); // hope this doesn't wrap around + end; + + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun))-1; + // We either need to adjust sum forward or blockNum back. + // sum := (dataStream^.maxDirect+superBlock^.blockSize-1) shr superBlock^.blockShift; + + // We need to decrease blockNum by the amount of blocks the direct portion took up. + // This can be calculated by dividing the maxDirect byte size by the block size. + + blockNum := blockNum - dword(dataStream^.maxDirect shr superBlock^.blockShift); + sum := 0; + for blockIndex := low(extents) to high(extents) do + for index := 0 to extentsUpperBound do + with extents[blockIndex]^[index] do + begin + if (blockNum < sum + len) then // found it + begin + offset := blockNum - sum; + setBlockRun(result, AG, start + offset, len - offset); + exit // was a break + end; + inc(sum, len); + end; // for/with +end; // TUbixFS.findBRunIndirect + +const printStuff:boolean = FALSE; + +function TUbixFS.findBRunDIndirect; +var + extents:^array[0..0] of TBlockRun; + sum, blockIndex, indirectIndex, index, offset:uInt32; + blocksPerIndirect:uInt32; // per indirect section (NUM_EXTENSION_BLOCKS * superBlock^.blockSize) === 4 x 1k block + blocksPerDIndirect:uInt32; // per single double indirect block === 1k block + runsPerBlock:uInt32; // runs per single block (blockSize div sizeof(TBlockRun)) + tmpBlockRun:TBlockRun; +begin + // NOTE: Basic error checking on whether superblock <> NIL isn't done here. + setBlockRun(result, 0, 0, 0); +if printStuff then writeln('Looking for block number ', blockNum); + + // find the indirect block that contains the block run we're looking for. + // This is done with some clever shifting + + // first adjust the blockNum, taking into account how many blocks are used + // by the direct and indirect sections +if printStuff then writeln('Original blockNum: ', blockNum); + blockNum := blockNum - dword(dataStream^.maxIndirect shr superBlock^.blockShift); +if printStuff then writeln('dataStream^.maxIndirect: ', dataStream^.maxIndirect); +if printStuff then writeln('Adjusted blockNum: ', blockNum); + // Each indirect block holds NUM_EXTENSION_BLOCKS*((superBlock^.blockSize*NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun)) blocks + // e.g. + // NUM_EXTENSION_BLOCKS = 4 + // blockSize = 1024*NUM_EXTENSION_BLOCKS + // sizeof(TBlockRun) = 8 + // blocks referenced per indirect block = 4 * (4096 / 8) === 2048 + // indirect block index = (block we're looking for) / (blocks referenced per indirect block) + // blockrun index in indirect block = ((block we're looking for) % (blocks referenced per indirect block)) div NUM_EXTENSION_BLOCK + + // If we're really *really* clever, we will only need to load two blocks to find this blockrun. + // So let's begin the cleverness. + tmpBlockRun := dataStream^.doubleIndirect; +if printStuff then with tmpBlockRun do + writeln('dataStream^.doubleIndirect: ', AG, ':', start, ':', len); + + runsPerBlock := superBlock^.blockSize div sizeof(TBlockRun); +if printStuff then writeln('runsPerBlock: ', runsPerBlock); + + blocksPerIndirect := NUM_EXTENSION_BLOCKS * ((superBlock^.blockSize * NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun)); +if printStuff then writeln('blocksPerIndirect: ', blocksPerIndirect); + blocksPerDIndirect := blocksPerIndirect * runsPerBlock; +if printStuff then writeln('blocksPerDIndirect: ', blocksPerDIndirect); + assert(blocksPerIndirect <> 0); // since this would div by 0 below anyway + assert(blocksPerDIndirect <> 0); + + blockIndex := blockNum div blocksPerDIndirect; +if printStuff then writeln('blockIndex: ', blockIndex); + index := (blockNum mod blocksPerDIndirect) div blocksPerIndirect; +if printStuff then writeln('index: ', index); + assert(blockIndex < NUM_EXTENSION_BLOCKS); // this would mean we're way outside the range of possible addressable blocks + inc(tmpBlockRun.start, blockIndex); + extents := loadBlock(tmpBlockRun); + assert(extents <> NIL); + + tmpBlockRun := extents^[index]; +if printStuff then with tmpBlockRun do + writeln('extents^[', index, ']: ', AG, ':', start, ':', len); + + // now find the proper indirect sub-block + + blockIndex := (blockNum mod blocksPerIndirect) div (NUM_EXTENSION_BLOCKS*runsPerBlock); +if printStuff then writeln('blockIndex: ', blockIndex); + assert(blockIndex < NUM_EXTENSION_BLOCKS); + + // adjust the double indirect block address + inc(tmpBlockRun.start, blockIndex); + extents := loadBlock(tmpBlockRun); + assert(extents <> NIL); + + // now find which indirect run references the block we're looking for + index := (blockNum mod (runsPerBlock*NUM_EXTENSION_BLOCKS)) div NUM_EXTENSION_BLOCKS; +if printStuff then writeln('index: ', index); +(* + blockIndex := (index mod blocksPerIndirect) div (superBlock^.blockSize div sizeof(TBlockRun)); + assert(blockIndex < NUM_EXTENSION_BLOCKS); + + index := (blockNum mod blocksPerIndirect) div NUM_EXTENSION_BLOCKS; + + tmpBlockRun := extents[ + i := blockNum div (NUM_EXTENSION_BLOCKS * ((superBlock^.blockSize*NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun))); + // i should now hold which entry in the double indirect block we need to load + assert(i < ((superBlock^.blockSize*NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun))); + + // load the proper indirect block beneath the double indirect block + extents := loadBlock(extents^[i]); + assert(extents <> NIL); + + // now seek into this indirect block by taking the modulus of the number + // of blocks referenced by an indirect block + i := (blockNum mod (NUM_EXTENSION_BLOCKS * ((superBlock^.blockSize*NUM_EXTENSION_BLOCKS) div sizeof(TBlockRun)))) div NUM_EXTENSION_BLOCKS; +*) + with extents^[index] do + begin + // extents^[i] is a block extent inside an indirect block which + // was referenced by the double indirect block. Now we need to see + // which block inside this extent we wanted. We do that by taking the + // modulus of the requested block number by the number of blocks + // referenced in each blockrun. BeFS used 4 per; and as of writing + // this comment that is what I'm using. + offset := blockNum and (NUM_EXTENSION_BLOCKS-1); +if printStuff then writeln('offset: ', offset); + setBlockRun(result, AG, start + offset, len - offset); + end; // with + +end; // TUbixFS.findBRunDIndirect + +function TUbixFS.freeUsedBlocks; +var + FBL:PBTree; + tmpBlockRun, leftBlockRun, rightBlockRun:TBlockRun; + searchRec:BTreeSearchRec; + mergeLeft, mergeRight:boolean; + + u:uPtr; +begin + result := -1; // failure by default + // basic sanity checks + if (superBlock = NIL) or (blockRun.len = 0) then exit; + if not isValidIAddr(superBlock^.BAT) then exit; + +with blockRun do writeln('---- freeing used blocks: ', AG, ':', start, ':', len); + new(FBL, open(iAddrToStr(superBlock^.BAT), new(PUbixBTreeVFS, init(@self)))); + if (FBL = NIL) then exit; + FBL^.InstallUserFunctions(compareKeyBlockRun, copyKeyBlockRun, keySizeBlockRun); + + fillchar(u, sizeof(u), 0); // not sure what to put in here yet + + assert(FBL^.Insert(@blockRun, u)); // put in our block run + assert(FBL^.FindFirst(@blockRun, searchRec)); // yes, we look it right back up + if FBL^.FindPrev(searchRec) then + begin + assert(searchRec.key <> NIL); + leftBlockRun := TBlockRun(searchRec.key^); + mergeLeft := (leftBlockRun.len <> 0) and (leftBlockRun.start + leftBlockRun.len = blockRun.start); + end + else mergeLeft := FALSE; + + assert(FBL^.FindFirst(@blockRun, searchRec)); // We're looking this one up a lot + + if FBL^.FindNext(searchRec) then + begin + assert(searchRec.key <> NIL); + rightBlockRun := TBlockRun(searchRec.key^); + mergeRight := (rightBlockRun.len <> 0) and (blockRun.start + blockRun.len = rightBlockRun.start) + end + else mergeRight := FALSE; + + if (mergeLeft or mergeRight) then + begin + // this blockRun merges two other free spaces into a single unified space + tmpBlockRun := blockRun; + if mergeLeft then + begin + dec(tmpBlockRun.start, leftBlockRun.len); + inc(tmpBlockRun.len, leftBlockRun.len); + end; + if mergeRight then inc(tmpBlockRun.len, rightBlockRun.len); + + // tmpBlockRun now holds the unified run. Delete the appropiate ones + if mergeLeft then FBL^.Delete(@leftBlockRun, u); + FBL^.Delete(@blockRun, u); + if mergeRight then FBL^.Delete(@rightBlockRun, u); + assert(FBL^.Insert(@tmpBlockRun, u)); + end; + + superBlock^.usedBlocks := superBlock^.usedBlocks - int64(blockRun.len); + FBL^.FindClose(searchRec); + dispose(FBL, done); + result := 0; // success +end; // TUbixFS.freeUsedBlocks +(* +function TUbixFS.freeUsedBlocks; +var + FBL:int8ArrayPtr; + FBLSize:uInt32; + startByte, endByte:uInt32; + bitCount, bitOffset:uInt32; + startBlock:uInt32; + byteOffset:uInt32; +begin + // Notes: + // 2005-04-27 mji + // freeUsedBlocks() will clear bits in the BAT that are associated + // with the blockRun passed in. To do this we load only the portion + // of the BAT we're going to modify. (As a side note, readDataStream() + // will load a minimum of one block anyway, so this may not really be + // much of an optimization). + + // XXX + // We *might* want to invalidate blocks in the cache that are marked as + // free. So far it seems to work fine without, but that might change. + + result := -1; + // basic sanity checks + if ((superBlock = NIL) or (blockRun.len = 0)) then exit; + +with blockRun do writeln('---- freeing used blocks: ', AG, ':', start, ':', len); + // Compute how much of the BAT we have to read in. + with blockRun, superBlock^ do + begin + startBlock := (AG shl AGShift) + start; + startByte := startBlock shr 3; // divide by 8 since it's a bitfield + endByte := (startBlock + len) shr 3; // divide by 8 since it's a bitfield + FBLSize := endByte - startByte + 1; + + GetMem(FBL, FBLSize); + assert(readDataStream(BAT, startByte, FBL, FBLSize) = 0); + bitOffset := 7-(startBlock and 7); + byteOffset := 0; + + for bitCount := 1 to len do + begin + FBL^[byteOffset] := FBL^[byteOffset] and not (1 shl bitOffset); + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + end; // for bitCount + // save back out the modified block + assert(writeDataStream(BAT, startByte, FBL, FBLSize) = 0); + + FreeMem(FBL, FBLSize); + usedBlocks := usedBlocks - len; + end; // with + + result := 0; +end; // TUbixFS.freeUsedBlocks +*) +function TUbixFS.getFileSize; +begin + result := 0; + if (inode = NIL) then exit; + result := inode^.blocks.size; +end; // TUbixFS.getFileSize + +function TUbixFS.getFileSpace; +begin + // getFileSpace() will return the size of the file's allocated space + result := 0; + if (inode = NIL) or (superBlock = NIL) then exit; + result := inode^.blocks.blockCount shl superBlock^.blockShift; +end; // TUbixFS.getFileSpace + +function TUbixFS.iAddrToStr; +var s:string; +begin + // Note: this won't work in C/C++ because strings are really null + // terminated character arrays. So, the B^Tree won't hold a string + // with nulls in it properly + length(s) := sizeof(iAddr); + move(iAddr, s[1], sizeof(iAddr)); + result := s; +end; // TUbixFS.iAddrToStr; + +function TUbixFS.loadBlock; +var + blockAddress:int64; + cacheNode:PCacheNode; + u:uPtr; + searchRec:BTreeSearchRec; +begin + result := NIL; // failure by default; + // 2005-04-29 mji + // I'm not really sure how much error checking to put in here. Chances are that + // if we're calling this function everything is fine. For now, with such limited + // use I'll keep it pretty light. I think that this will eventually be used for + // dealing with the block cache, so more checking might be required later. + if (dev = NIL) or (superBlock = NIL) or (blockCache = NIL) then exit; + + with blockAddr, superBlock^ do + blockAddress := (AG shl AGShift) + start; + + fillchar(u, sizeof(u), 0); // just to be safe + cacheNode := NIL; + // XXX ToDo + // Fix this so that if there is no cache, we only have one pointer + // Check for a full cache + + if (blockCache^.Find(@blockAddress, u)) then + begin + // block was in the cache. + assert(u.p <> NIL); + cacheNode := u.p; + assert(cacheNode^.data <> NIL); + // If we're going to honour the locked flag in the cacheNode, then + // we should put a mutex here + + // Now we need to update the blockTime. We do this by first deleting + // the cacheNode out of the blockTime cache. + blockTime^.Delete(@cacheNode^.lastUsed, u); // Delete old one + cacheNode^.lastUsed := getRDTSCTime(); // Get the new timestamp + blockTime^.Insert(@cacheNode^.lastUsed, u); // Insert with new time + result := cacheNode^.data; // return the data pointer + end + else + begin + // Requested block isn't in cache + + // XXX ToDo: + // Check to see if we've hit the maximum cache entry limit. If so, we + // need to drop off the least recently used (look for the first key + // in blockTime) and possibly save it to disk if it has been modified. + + if (blockCache^.getKeyCount() = maxCacheEntry) then + begin + blockTime^.getFirstKey(searchRec); // least recently used block + cacheNode := searchRec.value.p; // get the pointer from the value + assert(cacheNode <> NIL); + // I suppose we could write a syncBlock() function, since this + // code is also duplicated in sync(). + if (cacheNode^.dirty) then + begin + with cacheNode^ do + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + cacheNode^.dirty := FALSE; + end; // if dirty + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + blockTime^.Delete(@cacheNode^.lastUsed, u); + // We've deleted the cacheNode out of both the blockCache and + // the blockTime trees. We're going to re-use the cacheNode. + cacheNode^.address := blockAddress; + cacheNode^.lastUsed := getRDTSCTime(); + // No need to update data, dataSize, or dirty. We re-use the first two, + // and dirty will be set to FALSE above if necessary. Might need to + // reset locked if we're using that. + end + else + new(cacheNode, init(blockAddress, getRDTSCTime(), superBlock^.blockSize)); + + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + u.p := cacheNode; + if not (blockCache^.Insert(@blockAddress, u)) then + writeln('Error inserting ', blockAddress, ' into blockCache in loadBlock()'); + if not (blockTime^.Insert(@cacheNode^.lastUsed, u)) then + writeln('Error inserting into blockTime'); + // now we need to fill the data from the device + with superBlock^ do + dev^.read(cacheNode^.data, + blockAddress * (blockSize shr SECTOR_SHIFT), + blockSize shr SECTOR_SHIFT + ); // dev^.read + result := cacheNode^.data; + end; +end; // TUbixFS.loadBlock + +function TUbixFS.markUsedBlocks; +begin +end; // TUbixFS.markUsedBlocks +(* +function TUbixFS.markUsedBlocks; +var + FBL:uInt8ArrayPtr; + FBLSize:uInt32; + startByte, endByte:uInt32; + bitCount, bitOffset:uInt32; + startBlock:uInt32; + byteOffset:uInt32; +begin + + result := -1; + // basic sanity checks + if ((superBlock = NIL) or (blockRun.len = 0)) then exit; + +with blockRun do writeln('---- marking used blocks: ', AG, ':', start, ':', len); + + // Compute how much of the BAT we have to read in. + with blockRun, superBlock^ do + begin + startBlock := (AG shl AGShift) + start; + startByte := startBlock shr 3; + endByte := (startBlock + len) shr 3; + FBLSize := endByte - startByte + 1; + GetMem(FBL, FBLSize); + fillchar(FBL^, FBLSize, 0); // better safe than sorry + assert(readDataStream(BAT, startByte, FBL, FBLSize) = 0); + + bitOffset := 7-(startBlock and 7); + byteOffset := 0; + for bitCount := 1 to len do + begin + FBL^[byteOffset] := FBL^[byteOffset] or (1 shl bitOffset); + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + end; // for bitCount + assert(writeDataStream(BAT, startByte, FBL, FBLSize) = 0); + + FreeMem(FBL, FBLSize); + + usedBlocks := usedBlocks + len; + end; // with + result := 0; +end; // TUbixFS.markUsedBlocks +*) +function TUbixFS.nextAG; +begin + // there's no error we can throw from here if superblock is NIL, + // so don't bother to check. Just hope whomever calls this + // function has done some basic sanity checks + result := (AG+1) mod superBlock^.numAGs +end; // TUbixFS.nextAG + +function TUbixFS.pathToIAddr; + + function recursePath(path:PChar; len:integer; iAddr:inodeAddr):inodeAddr; + var + dirTree:PBTree; + u:uPtr; + nameSize:integer; + pfs:PUbixBTreeVFS; + begin + // if the string length is 0 then we have found the inode in the previous + // recurse, so return what we found. + if (len = 0) then + begin + result := iAddr; + exit; + end + else + setIAddr(result, 0, 0, 0); + // hm. If we're looking for something like ``/.'' then the ``/'' is stripped + // off in the main call leaving only ``.'', which means that this malformed + // path check would trigger. Same with /foo/bar if bar is a dir. + //if uInt8ArrayPtr(path)^[0] = ord('/') then exit; // malformed path + if not (isValidIAddr(iAddr)) then exit; // sanity check + nameSize := 0; + while (len > 0) and (uInt8ArrayPtr(path)^[nameSize] <> ord('/')) do + begin + inc(nameSize); + dec(len); + end; + uInt8ArrayPtr(path)^[nameSize+1] := 0; + if (len <> 0) then dec(len); + new(dirTree, open(IAddrToStr(iAddr), new(PUbixBTreeVFS, init(@self)))); + if (dirTree = NIL) then exit; // hm.. what happens to the PUbixBTreeVFS is this is NIL? + // note that the dirTree is a tree of BT_PCHAR, and searches are case sensitive. + // if the tree doesn't exist, Find() should return false. + if dirTree^.Find(path, u) then result := recursePath(path + nameSize + 1, len, u.iAddr); + dispose(dirTree, done); + end; // recursePath + +var + len:integer; + copyOfPath:PChar; +begin + setIAddr(result, 0, 0, 0); + + if (path = NIL) or (superBlock = NIL) then exit; + + len := strlen(path); + if (len = 0) then exit; // empty string + if uInt8ArrayPtr(path)^[0] <> ord('/') then exit; // malformed path + // we need to make a copy of the path since recursePath() will munge it + GetMem(copyOfPath, len+1); // Allocate some memory for the path + assert(copyOfPath <> NIL); + move(path^, copyOfPath^, len+1); // copy it + result := recursePath(copyOfPath+1, len-1, superBlock^.rootDir); + freemem(copyOfPath, len+1); +end; // TUbixFS.pathToIAddr + +procedure TUbixFS.printSuperBlock; +begin + if (superBlock = NIL) then exit; + with superBlock^ do + begin + writeln('superBlock^.name........... ', name); + writeln('superBlock^.magic1......... ', hex(magic1)); + writeln('superBlock^.fsByteOrder.... ', fsByteOrder); + writeln('superBlock^.blockSize...... ', blockSize); + writeln('superBlock^.blockShift..... ', blockShift); + writeln('superBlock^.numBlocks...... ', numBlocks); + writeln('superBlock^.usedBlocks..... ', usedBlocks); + writeln('superBlock^.magic2......... ', hex(magic2)); + writeln('superBlock^.blocksPerAG.... ', blocksPerAG); + writeln('superBlock^.AGShift........ ', AGShift); + writeln('superBlock^.numAGs......... ', numAGs); + writeln('superBlock^.flags.......... ', hex(flags)); + writeln('superBlock^.magic3......... ', hex(magic3)); + with superBlock^.BAT do + writeln('superBlock^.BAT............ ', AG, ':', start, ':', len); + with superBlock^.rootDir do + writeln('superBlock^.rootDir........ ', AG, ':', start, ':', len); + end; +end; // TUbixFS.printSuperBlock + +function TUbixFS.saveBlock; +var + blockAddress:int64; + cacheNode:PCacheNode; + u:uPtr; + searchRec:BTreeSearchRec; +begin + + result := FALSE; // failure by default; + if (blockCache = NIL) or (blockTime = NIL) or (dev = NIL) or (superBlock = NIL) then exit; + with blockAddr, superBlock^ do + blockAddress := (AG shl AGShift) + start; + cacheNode := NIL; + fillchar(u, sizeof(u), 0); // best be safe + // first check to see if the blockAddress is in the cache + if (blockCache^.Find(@blockAddress, u)) then + begin + // block was in cache + cacheNode := u.p; + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + if (cacheNode^.data <> buf) then + begin + // The buffer with the data we want to save was not the same as the one + // in the cache. This means that it was not obtained with loadBlock(). + // We need to copy that pointer to our block. + move(buf^, cacheNode^.data^, cacheNode^.dataSize); + end; + // Now we need to update the blockTime. We do this by first deleting + // the cacheNode out of the blockTime cache. + blockTime^.Delete(@cacheNode^.lastUsed, u); // Delete old one + cacheNode^.lastUsed := getRDTSCTime(); // Get the new timestamp + blockTime^.Insert(@cacheNode^.lastUsed, u); // Insert with new time + end + else + begin + // block was not in cache + if (blockCache^.getKeyCount() = maxCacheEntry) then + begin + blockTime^.getFirstKey(searchRec); + cacheNode := searchRec.value.p; + assert(cacheNode <> NIL); + // I suppose we could write a syncBlock() function, since this + // code is also duplicated in sync(). + if (cacheNode^.dirty) then + begin + with cacheNode^ do + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + cacheNode^.dirty := FALSE; + end; // if dirty + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + blockTime^.Delete(@cacheNode^.lastUsed, u); + // We've deleted the cacheNode out of both the blockCache and + // the blockTime trees. We're going to re-use the cacheNode. + cacheNode^.address := blockAddress; + cacheNode^.lastUsed := getRDTSCTime(); + // No need to update data, dataSize, or dirty. We re-use the first two, + // and dirty will be set to FALSE above if necessary. Might need to + // reset locked if we're using that. + end + else + new(cacheNode, init(blockAddress, getRDTSCTime(), superBlock^.blockSize)); + + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + move(buf^, cacheNode^.data^, cacheNode^.dataSize); + u.p := cacheNode; + if not (blockCache^.Insert(@blockAddress, u)) then + writeln('Error inserting ', blockAddress, ' into blockCache in saveBlock()'); + if not (blockTime^.Insert(@cacheNode^.lastUsed, u)) then + writeln('Error inserting into blockTime'); + end; + cacheNode^.dirty := TRUE; // indicate the block has been modified + result := TRUE; +end; // TUbixFS.saveBlock + +function TUbixFS.isValidIAddr; +begin + result := (iAddr.len <> 0) and (iAddr.AG < superBlock^.numAGs); +end; // TUbixFS.isValidIAddr + +function TUbixFS.readDataStream; +var + tmpBlock:uInt8ArrayPtr; + blockRun:TBlockRun; + size, remainder:uInt32; + blockStart:uInt32; + blocksToRead:uInt32; + bytesToRead:uInt32; + inode:PUbixFSInode; +begin +// with iAddr do +// writeln('readDataStream(', AG, ':', start, ':', len, ', ', position, ', ', hex(dword(buf)), ', ', length, ');'); + result := -1; + if ((buf = NIL) or (superBlock = NIL)) then exit; + inode := loadBlock(iAddr); + tmpBlock := NIL; + size := length; + remainder := uInt32(position mod superBlock^.blockSize); + + if (remainder <> 0) then + begin + // the position we've been asked to read from doesn't align to + // a block size. We need to read in the previous block and pad + // the data + blockStart := uInt32(position shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + bytesToRead := min(size, (superBlock^.blockSize - remainder)); + move(tmpBlock^[remainder], buf^, bytesToRead); + inc(buf, bytesToRead); + dec(size, bytesToRead); + position := position + bytesToRead; + end; // if remainder != 0 + + // If the blockSize is always a power of 2, then you can eliminate the MOD and use + // an AND operation instead, e.g. remainder := size and (superBlock^.blockSize-1) + remainder := size mod superBlock^.blockSize; + dec(size, remainder); + + // Size is currently in bytes. The remainder variable above holds the number + // of remaining bytes (if any) outside full blocks we have to write. + + // convert the size into blocks + size := size shr superBlock^.blockShift; + + while (size > 0) do + begin + blockRun := findBRun(@inode^.blocks, dword(position shr superBlock^.blockShift)); + assert(blockRun.len > 0); + for blocksToRead := 0 to min(blockRun.len, size)-1 do + begin + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + move(tmpBlock^, buf^, superBlock^.blockSize); + dec(size); + inc(buf, superBlock^.blockSize); + inc(blockRun.start); // This might wrap around? + dec(blockRun.len); // might be unnecessary + end; // for + end; // while + + if (remainder <> 0) then + begin + // There is less than a block of data left to read. + blockStart := dword((position+length-remainder) shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + if (blockRun.len = 0) then + begin + printStuff := TRUE; + blockRun := findBRun(@inode^.blocks, uInt32(position shr superBlock^.blockShift)); + end; + + assert(blockRun.len > 0); + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + move(tmpBlock^, buf^, remainder); + end; + + result := 0; +end; // TUbixFS.readDataStream + +procedure TUbixFS.setBlockRun; +begin + blockRun.AG := AG; + blockRun.start := start; + blockRun.len := len; +end; // TUbixFS.setBlockRun + +procedure TUbixFS.setIAddr; +begin + inode.AG := AG; + inode.start := start; + inode.len := len; +end; // TUbixFS.setIAddr + +function TUbixFS.strToIAddr; +begin + // Note: this won't work in C/C++ because strings are really null + // terminated character arrays. So, the B^Tree won't hold a string + // with nulls in it properly + // begin fiendishly clever hack + move(s[1], result, sizeof(inodeAddr)); + // end fiendishly clever hack +end; // TUbixFS.strToIAddr + +function TUbixFS.shrinkFile; + + function shrinkDirect(var trimBlockCount:uInt32):boolean; + var + index:uInt32; + blockRun:TBlockRun; // for freeUsedBlocks() + begin + result := FALSE; + with inode^, blocks do + begin + dec(blockCount, trimBlockCount); // this is a little presumptious + maxDirect := maxDirect - (trimBlockCount shl superBlock^.blockShift); // so is this + for index := high(direct) downto low(direct) do + if (direct[index].len <> 0) then break; // find the last used one + assert(direct[index].len <> 0); + while (trimBlockCount <> 0) do + begin + if (direct[index].len >= trimBlockCount) then + begin + blockRun := direct[index]; // make a copy + dec(direct[index].len, trimBlockCount); // subtract from the len + if (direct[index].len = 0) then // check to see if that was the whole run + setBlockRun(direct[index], 0, 0, 0) // yup, mark this entry as blank + else // else, nope .. update blockRun for freeUsedBlocks() + setBlockRun(blockRun, blockRun.AG, blockRun.start + direct[index].len, blockRun.len - direct[index].len); + freeUsedBlocks(blockRun); // free the blockRun we just trimmed + trimBlockCount := 0; // all done + break; // end the while + end + else + begin + dec(trimBlockCount, direct[index].len); // subtract the length of this run from the total trim amount + freeUsedBlocks(direct[index]); // free this blockRun + setBlockRun(direct[index], 0, 0, 0); // mark this entry as blank + end; + // keep going until we've hit bottom or while trimBlockCount <> 0 + if (index = low(direct)) then break else dec(index); + end; // while + end; // with + assert(trimBlockCount = 0); // Better be 0. + result := TRUE; + end; // local function shrinkDirect + + function shrinkIndirect(var trimBlockCount:uInt32):boolean; + var + extents:array[0..NUM_EXTENSION_BLOCKS-1] of ^array[0..0] of TBlockRun; + extentsUpperBound, blockIndex, index:uInt32; + tmpBlockRun:TBlockRun; + begin + result := FALSE; // failure by default; + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun))-1; + + if not isValidIaddr(inode^.blocks.indirect) then + begin + writeln('For some reason you called shrinkIndirect() when there was no valid indirect block.'); + exit; + end; + + // load the indirect block + tmpBlockRun := inode^.blocks.indirect; + for blockIndex := low(extents) to high(extents) do + begin + extents[blockIndex] := loadBlock(tmpBlockRun); + assert(extents[blockIndex] <> NIL); + inc(tmpBlockRun.start); // hope this doesn't wrap around + end; // for blockIndex + + for blockIndex := high(extents) downto low(extents) do + if (extents[blockIndex]^[0].len <> 0) then break; + + for index := extentsUpperBound downto 0 do + if (extents[blockIndex]^[index].len <> 0) then break; + assert(extents[blockIndex]^[index].len <> 0); // this would be odd + + with inode^, blocks do + begin + while (trimBlockCount <> 0) do + begin + if (extents[blockIndex]^[index].len >= trimBlockCount) then + begin + tmpBlockRun := extents[blockIndex]^[index]; // make a copy + dec(extents[blockIndex]^[index].len, trimBlockCount); // subtract from the len + maxIndirect := maxIndirect - (trimBlockCount shl superBlock^.blockShift); // update inode^.blocks.maxIndirect + dec(blockCount, trimBlockCount); // update inode^.blocks.blockCount + if (extents[blockIndex]^[index].len = 0) then // check to see if that was the whole run + begin + // Reclaim this entry. But, if the index is 0, then + // we've trimmed away the indirect section completely. + if (index = 0) then + begin + if (blockIndex = 0) then // toast the indirect block + begin + freeUsedBlocks(indirect); + setIAddr(indirect, 0, 0, 0); + end + else // just move to the previous section of the indirect block + begin + // XXX we probably need to save out extents[blockIndex] right here + dec(blockIndex); + index := extentsUpperBound; + end; + end + else // else just clear this entry + setBlockRun(extents[blockIndex]^[index], 0, 0, 0) // yup it was the whole run, mark this entry as blank + end + else // else, nope .. update tmpBlockRun for freeUsedBlocks() + setBlockRun(tmpBlockRun, tmpBlockRun.AG, tmpBlockRun.start + extents[blockIndex]^[index].len, tmpBlockRun.len - extents[blockIndex]^[index].len); + freeUsedBlocks(tmpBlockRun); // free the blockRun we just trimmed + trimBlockCount := 0; // all done + break; // end the while + end + else + begin + dec(trimBlockCount, extents[blockIndex]^[index].len); // subtract the length of this run from the total trim amount + freeUsedBlocks(extents[blockIndex]^[index]); // free this blockRun + setBlockRun(extents[blockIndex]^[index], 0, 0, 0); // mark this entry as blank + // This branch means that trimBlockCount is not 0 yet (relevant to code below) + end; + // keep going until we've hit bottom or while trimBlockCount <> 0 + if (index = 0) then + begin + if (blockIndex = 0) then + begin + // If we're here, then the indirect block is no longer needed + // and we need to scan through the direct section. Toast the + // indirect block + freeUsedBlocks(indirect); + setBlockRun(indirect, 0, 0, 0); + result := shrinkDirect(trimBlockCount); + exit; + end + else + begin + dec(blockIndex); + index := extentsUpperBound; + end; + end + else + dec(index); + end; // while + // XXX + // This next piece of code should go away and the proper pieces should be saved above. + // Since we have caching at the block level, we can go ahead and save blocks when they + // change without worrying too much about speed. + if isValidIAddr(indirect) then // we might have removed the indirect section altogether + begin + tmpBlockRun := indirect; + for blockIndex := low(extents) to high(extents) do + begin + assert(saveBlock(tmpBlockRun, extents[blockIndex])); + inc(tmpBlockRun.start); // hope this doesn't wrap around + end; + end; // if isValidIAddr(indirect) + end; // with + result := TRUE; // success + end; // local function shrinkIndirect + + function shrinkDIndirect(var trimBlockCount:uInt32):boolean; + var + extents:^array[0..0] of TBlockRun; + indirectExtents:^array[0..0] of TBlockRun; + extentsIndex, indirectExtentsIndex:uInt32; + extentsUpperBound:uInt32; + tmpBlockRun:TBlockRun; + begin + result := FALSE; // failure by default + + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun)) - 1; + assert(isValidIAddr(inode^.blocks.doubleIndirect)); + extents := loadBlock(inode^.blocks.doubleIndirect); + assert(extents <> NIL); + for extentsIndex := extentsUPperBound downto 0 do + if (extents^[extentsIndex].len <> 0) then break; + assert(extents^[extentsIndex].len <> 0); + indirectExtents := loadBlock(extents^[extentsIndex]); + assert(indirectExtents <> NIL); + for indirectExtentsIndex := extentsUpperBound downto 0 do + if (indirectExtents^[indirectExtentsIndex].len <> 0) then break; + assert(indirectExtents^[indirectExtentsIndex].len <> 0); + while (trimBlockCount >= NUM_EXTENSION_BLOCKS) do + begin + dec(trimBlockCount, indirectExtents^[indirectExtentsIndex].len); + dec(inode^.blocks.blockCount, indirectExtents^[indirectExtentsIndex].len); + with inode^, blocks do + maxDIndirect := maxDIndirect - (indirectExtents^[indirectExtentsIndex].len shl superBlock^.blockShift); + freeUsedBlocks(indirectExtents^[indirectExtentsIndex]); // free the blockRun + if (indirectExtentsIndex = 0) then + begin + freeUsedBlocks(extents^[extentsIndex]); // free this indirect block + if (extentsIndex = 0) then + begin + freeUsedBlocks(inode^.blocks.doubleIndirect); // free the double indirect block + setBlockRun(inode^.blocks.doubleIndirect, 0, 0, 0); + // XXX + // We should put a check to make sure inode^.blocks.maxDIndirect is equal to maxIndirect + with inode^, blocks do + assert(maxDIndirect = maxIndirect); + inode^.blocks.maxDIndirect := 0; + result := shrinkIndirect(trimBlockCount); + exit; + end + else + begin + dec(extentsIndex); + assert(saveBlock(inode^.blocks.doubleIndirect, extents)); // XXX this isn't right + indirectExtents := loadBlock(extents^[extentsIndex]); + assert(indirectExtents <> NIL); + indirectExtentsIndex := extentsUpperBound; + end; // else + end + else + dec(indirectExtentsIndex); + end; // while + result := TRUE; // success + end; // local function shrinkDIndirect + + function shrinkDataStream(var trimBlocks:uInt32):boolean; + begin + if (inode^.blocks.maxDIndirect > 0) then result := shrinkDIndirect(trimBlocks) else + if (inode^.blocks.maxIndirect > 0) then result := shrinkIndirect(trimBlocks) + else result := shrinkDirect(trimBlocks); + end; // local function shrinkDataStream +var + blocksToTrim:uInt32; + +begin // shrinkFile() + // XXX ToDo + // Figure out which pieces of the datastream need to be saved + // in shrinkIndirect() and shrinkDIndirect() + + result := FALSE; // failure by default + if (inode = NIL) or (superBlock = NIL) then exit; + + // start with the original block count + blocksToTrim := inode^.blocks.blockCount; + // Subtract off how much space the file actually takes up. + dec(blocksToTrim, uInt32((inode^.blocks.size+(superBlock^.blockSize-1)) shr superBlock^.blockShift)); + + // Do a quick check to see if we're in double indirect blocks. If we are, and the amount to trim + // is less than NUM_EXTENSION_BLOCKS then we don't actually need to do anything. + // Also do a quick check to see if blocksToTrim is 0 .. which means we really don't have to do anything. + if (blocksToTrim = 0) or (isValidIAddr(inode^.blocks.doubleIndirect) and (blocksToTrim < NUM_EXTENSION_BLOCKS)) then + begin + result := TRUE; // Technically there was no failure + exit; + end; + result := shrinkDataStream(blocksToTrim); + assert(saveBlock(inode^.inodeNum, inode)); +end; // TUbixFS.shrinkFile + +procedure TUbixFS.sync; +var + searchRec:BTreeSearchRec; +begin + // XXX ToDo: + // Need to mark the superblock as clean. + + if (blockCache = NIL) or (dev = NIL) then exit; + // We sync out the blocks in ascending order from the blockCache. + // To do this we examine each entry to see if the block is dirty. + // If it is, we write it out to the device and set dirty to FALSE. + if (blockCache^.getFirstKey(searchRec)) then + repeat + syncBlock(searchRec.value.p); + until not blockCache^.FindNext(searchRec); + // Also write out the superblock. + dev^.write(superBlock, 1, 1); +end; // TUbixFS.sync + +function TUbixFS.syncBlock; +begin + result := FALSE; + if (dev = NIL) or (cacheNode = NIL) then exit; + with cacheNode^ do + if (dirty) then + begin + assert(data <> NIL); + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + dirty := FALSE; + end; // if dirty + result := TRUE; +end; // TUbixFS.syncBlock +{$IFDEF GRAPHICS} + {$I graphics.pas} +{$ENDIF} + +function TUbixFS.vfs_close; +var + u:uPtr; + vnid:PVnid; + inode:PUbixfsInode; +begin + result := -1; + if (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + if not (vnids^.Find(@fileDescriptors^[handle].iAddr, u)) then writeln('Could not find iAddr in vnids'); + vnid := u.p; + if (vnid = NIL) then + begin + writeln('vnid is NIL'); + exit; + end; + + with vnid^ do + begin + dec(refCount); + if (refCount = 0) then + begin + // refCount has fallen to 0. If the file is deleted we should take care of it here + inode := loadBlock(fileDescriptors^[handle].iAddr); + // if file is deleted then inode^.blocks.size = 0; + assert(inode <> NIL); + // shrink the file + assert(shrinkFile(inode)); + vnids^.Delete(@inode^.inodeNum, u); + setIAddr(fileDescriptors^[handle].iAddr, 0, 0, 0); + end; + end; + if not vNodeQueue^.enqueue(handle) then writeln('Error enqueuing handle back into vNodeQueue'); + result := 0; +end; // TUbixFS.vfs_close + +function TUbixFS.vfs_fCreate; +var + inode:PUbixFSInode; + dirTree:PBTree; + dirIAddr, fileIAddr:inodeAddr; + u:uPtr; + searchRec:BTreeSearchRec; + len : integer; + path:PChar; + fileNamePtr:PChar; + curPos:integer; + vnid:PVnid; + handle:integer; +begin + result := -1; // error by default + if (filename = NIL) or (superBlock = NIL) then exit; + // we need to split the filename into it's component path and filename pieces. + // From there we need to see if the file exists. If it does then I guess we + // should truncate the file and load the inode. Otherwise allocate a new inode + len := strlen(filename); + if (len = 0) then exit; + if uInt8ArrayPtr(filename)^[0] <> ord('/') then exit; // malformed path + GetMem(path, len+1); + strCopy(path, filename); + curPos := len; + + setIAddr(dirIAddr, 0, 0, 0); // zero out the inode address for the directory + setIAddr(fileIAddr, 0, 0, 0); // zero out the inode address for the file + + while (curPos > 0) and (uInt8ArrayPtr(path)^[curPos] <> ord('/')) do dec(curPos); + fileNamePtr := path + curPos + 1; + if (curPos = 0) then + begin + // file is in the root dir + writeln('path: /'); + writeln('filename: ', fileNamePtr); + dirIAddr := pathToIAddr('/'); + end + else + begin + uInt8ArrayPtr(path)^[curPos] := 0; // null out the separator + writeln('Path: ', path); + writeln('Filename: ', fileNamePtr); + dirIAddr := pathToIAddr(path); + end; + + with dirIAddr do + writeln('iaddr to path: ', AG, ':', start, ':', len); + assert(isValidIAddr(dirIAddr)); + if not isValidIAddr(dirIAddr) then exit; + // should probably check to make sure that we're opening a directory and not a file + new(dirTree, open(IAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self)))); + if (dirTree = NIL) then exit; + // We need to ascertain if the file is existing or new. + // If the file exists, we should truncate the file + // If the file is new, we need to create a new inode and write the entry to the directory + // Then we need to allocate a file descriptor and store it somewhere. + if dirTree^.Find(fileNamePtr, u) then + begin + writeln(fileNamePtr, ' already exists'); + // XXX ToDo: + // Set the filesize to 0 and call shrinkFile on the inode. + // So, uhm, what happens if the file is already open? + end + else + begin + writeln('creating new file: ', fileNamePtr); + writeln('allocating new inode'); + inode := allocInode(dirIAddr); // alloc a new inode (pass in the parent iaddr) + move(fileNamePtr^, inode^.name, strlen(filenamePtr)); + assert(inode <> NIL); // make sure it isn't nil + if (inode = NIL) then exit; + assert(saveBlock(inode^.inodeNum, inode)); // save the new inode + u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr + // now add the file to the directory + writeln('Inserting ', fileNamePtr, ' into directory returned ', dirTree^.Insert(fileNamePtr, u)); + end; + dispose(dirTree, done); + FreeMem(path, len+1); + + // This file doesn't have an associated vnid. Create a new one. + new(vnid); + vnid^.refCount := 1; + vnid^.iAddr := inode^.inodeNum; + vnid^.mode := 0; // XXX ? + u.p := vnid; + if not (vnids^.insert(@inode^.inodeNum, u)) then writeln('Could not insert into vnids'); + + if not vNodeQueue^.dequeue(handle) then + begin + writeln('Out of file descriptors!'); + exit; + end; + + with fileDescriptors^[handle] do + begin + TID := 0; // ThreadID + iAddr := inode^.inodeNum; + position := 0; + flags := 0; // XXX ? + end; + result := handle; +end; // TUbixFS.vfs_fCreate + +function TUbixFS.vfs_fExist; +begin + // XXX ToDo: + // find out why fExist('/./.') fails + // fExist('/.') no longer fails + + result := -1; + if ((superBlock = NIL) or (path = NIL)) then exit; + if (not isValidIAddr(superBlock^.rootDir)) then exit; + if isValidIAddr(pathToIAddr(path)) then result := 0; +end; // TUbixFS.vfs_fExist + +function TUbixFS.vfs_format; +var + sector:array[0..511] of byte; + fs:PUbixFS; + logicalBlocks, fsBATSize, fsBATBlocks:uInt32; + sb:PdiskSuperBlock; + inode:PubixfsInode; + i, fsBlockShift, lBlockShift, nodeSize, keyLength:integer; + blockRun:TBlockRun; + FBL:PBTree; + remainderBlocks:uInt32; + u:uPtr; + blockBit:shortint; +begin + result := -1; + if (device = NIL) then exit; + if (fsBlockSize < 1024) or (fsBlockSize > 8192) then + begin + writeln('Blocksize must be between 1024 and 8192'); + exit; + end; + // zero out the sector + fillchar(sector, sizeof(sector), 0); + + writeln('device^.sectors: ', device^.sectors); + + if not quick then + begin + system.write('clearing device... '); + + for i := 0 to device^.sectors-1 do + device^.write(@sector, i, 1); + + writeln('done'); + end; // if !quick + + + fsBlockShift := 10; {minimum is 1024} + while (1 shl fsBlockShift < fsBlockSize) do inc(fsBlockShift); + + // allocate a new superBlock and clear it + + new(sb); + if (sb = NIL) then exit; + fillchar(sb^, sizeof(TDiskSuperBlock), 0); + + // device^.sectors is the number of 512 byte sectors + + lBlockShift := fsBlockSize shr 9; + writeln('lBlockShift: ', lBlockShift); + logicalBlocks := device^.sectors div lBlockShift; // logical blocks + writeln('logical blocks: ', logicalBlocks); + + with sb^ do + begin + strcopy(name, 'UbixFS'); + magic1 := UBIXFS_MAGIC_1; + fsByteOrder := 0; + blockSize := fsBlockSize; + blockShift := fsBlockShift; + numBlocks := logicalBlocks; + usedBlocks := 3; // XXX Based on what we allocate below for the new BAT system! + inodeSize := fsBlockSize; + magic2 := UBIXFS_MAGIC_2; + blocksPerAG := min(65536, blockSize shl 3); + AGShift := 10; // realistically it should be a min of 13 + while (1 shl AGShift <> blocksPerAG) do inc(AGShift); + numAGs := (dword(numBlocks)+(blocksPerAG-1)) div blocksPerAG; + + flags := UBIXFS_CLEAN; + setBlockRun(logBlocks, 0, 0, 0); + logStart := 0; + logEnd := 0; + magic3 := UBIXFS_MAGIC_3; + setIAddr(indicies, 0, 0, 0); + setIAddr(rootDir, 0, 0, 0); + end; // with + // create the free block list inode + + getmem(inode, fsBlockSize); // make the inode the same size as a block + if (inode = NIL) then exit; + + fillchar(inode^, sb^.inodeSize, 0); + + with inode^ do + begin + magic := UBIXFS_INODE_MAGIC; + + // inodes point to themselves + setIAddr(inodeNum, 0, sb^.logBlocks.len+1, 1); // the +1 for the start might be wrong + setIAddr(parent, 0, 0, 0); + + // If the partition is larger than 550GB we will need to allocate more than + // one blockRun for the BAT. + +//oldBAT -- fsBATSize := logicalBlocks shr 3; // compute how many bytes the BAT takes up +//oldBAT -- fsBATBlocks := (fsBATSize + fsBlockSize-1) div fsBlockSize; +//oldBAT -- writeln('fsBATSize: ', fsBATSize); +//oldBAT -- writeln('fsBATBlocks: ', fsBATBlocks); +//oldBAT -- setBlockRun(blocks.direct[0], 0, inodeNum.start+1, fsBATBlocks); + +//oldBAT -- blocks.size := (fsBATBlocks * fsBlockSize); // round the file size up to the nearest block size +//oldBAT -- blocks.maxDirect := blocks.size-1; +//oldBAT -- blocks.blockCount := fsBATBlocks; + blocks.size := 0; + blocks.maxDirect := fsBlockSize-1; + blocks.blockcount := 1; + setBlockRun(blocks.direct[0], 0, inodeNum.start+1, 1); // manually allocate 1 block + + uid := 0; + gid := 0; + mode := 0; + strcopy(name, 'Block Allocation Tree'); + flags := INODE_LOGGED; + + setIAddr(attributes, 0, 0, 0); + inodeSize := sb^.inodeSize; + + end; {with} + sb^.BAT := inode^.inodeNum; + writeln('writing out superblock to sector 1'); + device^.write(sb, 1, 1); + // note that the superblock does not contain proper inode addresses for the + // BAT and rootDir. Those are sync'd to disk in the destructor after they've been + // created below. + + system.write('Writing out BAT inode... '); + // write out the Block Allocation Table inode + with inode^.inodeNum, sb^ do + device^.write(inode, + ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT), + inodeSize shr SECTOR_SHIFT + ); // device^.write + writeln('done'); + // Okay, the superblock is the only piece of information needed + // to mount an UbixFS partition + new(fs, init(device)); + assert(fs <> NIL); + assert(fs^.vfs_init() = 0); + + // The freshly formatted parition is now mounted. + // From here we need to create: + // BAT (Block Allocation Table) file contents + // journal + // root dir + new(FBL, init(iAddrToStr(sb^.BAT), BAT_PAGE_SIZE, BT_CUSTOM, new(PUbixBTreeVFS, init(fs)))); + if (FBL = NIL) then + begin + writeln('Error creating Block Allocation Tree in vfs_format()'); + halt; + end; + + FBL^.InstallUserFunctions(compareKeyBlockRun, copyKeyBlockRun, keySizeBlockRun); + + fillchar(u, sizeof(u), 0); + + setBlockRun(blockRun, 0, 0, 0); // 0th AG tag + assert(FBL^.Insert(@blockRun, u)); // 0th AG marker +writeln(dword(sb^.numBlocks)); +writeln(sb^.numAGs); + remainderBlocks := dword(sb^.numBlocks-sb^.usedBlocks); + i := inode^.blocks.direct[0].start+1; + setBlockRun(blockRun, 0, i, min((sb^.blocksPerAG-i), remainderBlocks)); + dec(remainderBlocks, blockRun.len); +with blockRun do + writeln('0th AG free block section: ', AG, ':', start, ':', len); + + FBL^.Insert(@blockRun, u); // 0th AG free block list + for i := 1 to sb^.numAGs-1 do + begin + setBlockRun(blockRun, i, 0, 0); // AG tags + assert(FBL^.Insert(@blockRun, u)); + blockRun.start := 1; + blockRun.len := min(remainderBlocks, sb^.blocksPerAG-1); + assert(FBL^.Insert(@blockRun, u)); + dec(remainderBlocks, min(remainderBlocks, sb^.blocksPerAG)); + sb^.usedBlocks := sb^.usedBlocks + 1; + end; // for i +writeln('remainderBlocks: ', remainderBlocks); +fbl^.printWholeTree('bat1.txt'); +assert(remainderblocks = 0); + dispose(FBL, done); + (* + // inode still points to the BAT. Clear out the file in case people decided to + // use quick formatting + for i := 0 to (dword(inode^.blocks.size) div sizeof(sector))-1 do + fs^.writeDataStream(sb^.BAT, i*sizeof(sector), @sector, sizeof(sector)); + setBlockRun(blockRun, 0, 0, 1); + fs^.markusedBlocks(blockRun); // mark superBlock block as used + // mark the journal as used here + fs^.markUsedBlocks(sb^.BAT); // mark the BAT inode as used + // now mark the BAT file blocks as used. Note that if the BAT takes up + // more than one blockRun (unlikely, but not impossible) then this code + // will have to check for that. + writeln('BAT inode blocks.direct[0].len: ', inode^.blocks.direct[0].len); + fs^.markUsedBlocks(inode^.blocks.direct[0]); + + // If the number of blocksPerAG doesn't divide evenly into the + // number of logical blocks, mark out all the "remainder" blocks + // We need to mark the blocks as used in the BAT manually because + // if we do it through markUsedBlocks() it will change the usedBlocks + // count. + + remainderBlocks := (fsBATBlocks shl sb^.blockShift) - fsBATSize; + writeln('RemainderBlocks: ', remainderBlocks); + if (remainderBlocks <> 0) then + begin + // check if the remainder blocks are evenly divisible by 8 + if (remainderBlocks and 7 <> 0) then + begin + blockBit := uInt8(1 shl (remainderBlocks and 7)) -1; + fs^.writeDataStream(sb^.BAT, fsBatSize, @blockBit, 1); + blockBit := -1; + for i := fsBatSize+1 to (fsBATBlocks shl sb^.blockShift)-1 do + fs^.writeDataStream(sb^.BAT, i, @blockBit, 1); + end + else + begin + blockBit := -1; + for i := fsBatSize to (fsBATBlocks shl sb^.blockShift)-1 do + fs^.writeDataStream(sb^.BAT, i, @blockBit, 1); + end; + end; +*) + fs^.vfs_mkdir('/'); + if (fs^.vfs_fExist('/.') = -1) then + writeln('Warning! /. doesn''t exist') + else + writeln('fExist(''/.'') returned properly'); + if (fs^.vfs_fExist('/nofile.dat') <> -1) then + writeln('Warning! /nofile.dat exists?') + else + writeln('fExist(''/nofile.dat'') returned properly'); + + if (fs^.vfs_fExist('/nodir/nofile.dat') <> -1) then + writeln('Warning! /nodir/nofile.dat exists?') + else + writeln('fExist(''/nodir/nofile.dat'') returned properly'); + +// writeln('fs^.vfs_fExist(''/.'') returned: ', fs^.vfs_fExist('/.')); + writeln('fs^.vfs_fExist(''/nofile.dat'') returned: ', fs^.vfs_fExist('/nofile.dat')); + fs^.printSuperBlock; + + dispose(fs, done); + writeln('Format complete!'); + dispose(sb); + freemem(inode, fsBlockSize); + +end; // TUbixfS.vfs_format + +function TUbixFS.vfs_init; +var index:uInt32; +begin + // This function should probably not be called more than once without + // calling the appropiate shutdown code + writeln('TUbixFS.vfs_init()'); + result := -1; + if (dev = NIL) then exit; + new(superBlock); + assert(superBlock <> NIL); + if (superBlock = NIL) then exit; + fillchar(superBlock^, sizeof(superBlock^), 0); // better safe than sorry + // read in the superBlock. It's always the 1st block on the partition (counting from 0) + system.write('reading in superBlock... '); + dev^.read(superBlock, 1, 1); + writeln('done'); + + assert(superBlock^.magic1 = UBIXFS_MAGIC_1); + assert(superBlock^.magic2 = UBIXFS_MAGIC_2); + assert(superBlock^.magic3 = UBIXFS_MAGIC_3); + assert(strcomp(superBlock^.name, 'UbixFS') = 0); + assert((1 shl superBlock^.blockShift) = superBlock^.blockSize); + assert((1 shl superBlock^.AGShift) = superBlock^.blocksPerAG); + assert(superBlock^.flags = UBIXFS_CLEAN); + + // create the file decriptor tree + + + // The blockCache tree has a [key::value] pair of [block address::pointer to cacheNode]. + // The blockTime tree has a [key::value] pair of [last used time::pointer to cacheNode]. + + // create the block cache tree + new(blockCache, init('', 256, BT_INT64, NIL)); + // create the block last used time tree + // The last used time is obtained through the RDTSC instruction to give us a unique + // and increasing timestamp. When the cache is full we drop off the least recently + // used block. Because the tree is sorted, this will be the first entry. + new(blockTime, init('', 256, BT_INT64, NIL)); + + // Create the file descriptors and related stuff + + // vnids is a tree which has a [key::value] pair of [iAddr::pointer to vNode]. + new(vnids, init('', 256, BT_INT64, NIL)); + + // vnodeQueue is a queue which has a list of available FD handles + new(vNodeQueue, init(maxFDEntry, sizeof(uInt32))); + // fill the vNodeQueue with a list of all available FD handles + for index := 0 to maxFDEntry-1 do + vNodeQueue^.enqueue(index); + + // Allocate and clear the file descriptor array + GetMem(fileDescriptors, maxFDEntry*sizeof(TVNode)); + fillchar(fileDescriptors^, maxFDEntry*sizeof(TVNode), 0); + + result := 0; // success +end; // TUbixFS.vfs_init + +function TUbixFS.vfs_mkdir; +const + dot:array[0..1] of char = '.'#0; + dotdot:array[0..2] of char = '..'#0; +var + inode:PUbixFSInode; + dirIAddr:inodeAddr; + iAddr:inodeAddr; + bRun:TBlockRun; + len:integer; + dirTree:PbTree; + u:uPtr; + pathToParentDir:PChar; + dirNamePtr:PChar; + curPos:integer; + +begin + result := -1; + assert(path <> NIL); + assert(superBlock <> NIL); + if (path = NIL) or (superBlock = NIL) then exit; + + len := strlen(path); + assert(len <> 0); + if (len = 0) then exit; + + if (len = 1) and (uInt8ArrayPtr(path)^[0] = ord('/')) then + begin + writeln('Creating root directory'); + // wanting to create the root. Check to see if the root dir inode is empty + assert(not isValidIAddr(superBlock^.rootDir)); + if isValidIAddr(superBlock^.rootDir) then exit; // should set a proper error code here + setIAddr(iAddr, 0, 0, 0); // technically the "parent" iaddr + inode := allocInode(iAddr); // allocate an inode (parent points to la-la land) + assert(inode <> NIL); // make sure it isn't nil + if (inode = NIL) then exit; + inode^.mode := S_IFDIR; + inode^.flags := INODE_LOGGED; + superBlock^.rootDir := inode^.inodeNum; // assign the root dir + strcopy(inode^.name, '/'); // XXX Needs to be an attribute + assert(saveBlock(superBlock^.rootDir, inode)); // save the inode + u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr + + // open the root dir + with superBlock^.rootDir do + writeln('Root dir inode: ', AG, ':', start, ':', len); + writeln('Opening root dir tree'); + new(dirTree, init(iAddrToStr(superBlock^.rootDir), 1024, BT_PCHAR, new(PUbixBTreeVFS, init(@self)))); + writeln('Inserting ''.'''); + // Get ready to insert the '.' :: inode^.inodeNum key :: value pair + writeln('dirTree^.Insert(@dot, u) returned ', dirTree^.Insert(@dot, u)); // '.' points to ourself + writeln('dirTree^.Insert(@dotDot, u) returned ', dirTree^.Insert(@dotDot, u)); // '..' points to ourself, too + dispose(dirTree, done); // close and save the directory + end + else + begin + // subdir support goes here + setIAddr(dirIAddr, 0, 0, 0); + len := strlen(path); + if (len = 0) then exit; + if uInt8ArrayPtr(path)^[0] <> ord('/') then exit; // malformed path + GetMem(pathToParentDir, len+1); + strCopy(pathToParentDir, path); + curPos := len; + while (curPos > 0) and (uInt8ArrayPtr(pathToParentDir)^[curPos] <> ord('/')) do dec(curPos); + dirNamePtr := pathToParentDir + curPos + 1; + if (curPos = 0) then + begin + writeln('New directory being created below root'); + writeln('dir name: ', dirNamePtr); + dirIAddr := superBlock^.rootDir; + end + else + begin + if (curPos <> 0) then uInt8ArrayPtr(pathToParentDir)^[curPos] := 0; // null out the last / separator + writeln('Path to parent dir: ', pathToParentDir); + writeln('dir name: ', dirNamePtr); + dirIAddr := pathToIAddr(pathToParentDir); + + end; + inode := allocInode(dirIAddr); // allocate an inode + assert(inode <> NIL); + if (inode = NIL) then exit; // set some appropiate error code (not sure what) + inode^.mode := S_IFDIR; + strcopy(inode^.name, dirNamePtr); + assert(saveBlock(inode^.inodeNum, inode)); + u.iAddr := inode^.inodeNum; + // open the parent dir + new(dirTree, open(iAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self)))); + writeln('dirTree^.Insert(@inode^.name, u) returned ', dirTree^.Insert(@inode^.name, u)); + dispose(dirTree, done); + // now open the subdir and insert '.' and '..' + new(dirTree, init(iAddrToStr(inode^.inodeNum), 1024, BT_PCHAR, new(PUbixBTreeVFS, init(@self)))); + // Get ready to insert the '.' :: inode^.inodeNum key :: value pair + writeln('dirTree^.Insert(@dot, u) returned ', dirTree^.Insert(@dot, u)); // '.' points to ourself + u.iAddr := dirIAddr; + writeln('dirTree^.Insert(@dotdot, u) returned ', dirTree^.Insert(@dotdot, u)); // '..' points to parent dir + dispose(dirTree, done); + FreeMem(pathToParentDir, len+1); + end; // else subdir + + result := 0; +end; // TUbixFS.vfs_mkdir + +function TUbixFS.vfs_read; +var + inode:PUbixFSInode; + u:uPtr; +begin + result := -1; + if (buffer = NIL) or (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + with fileDescriptors^[handle] do + begin + assert(readDataStream(iAddr, position, buffer, size) = 0); + position := position + int64(size); + result := size; + end; // with +end; // TUbixFS.vfs_read + +function TUbixFS.vfs_seek; +begin + result := -1; + if (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + fileDescriptors^[handle].position := newPosition; + result := 0; +end; // TUbixFS.vfs_seek + +function TUbixFS.vfs_unlink; +var + dirTree:PbTree; + iAddr:inodeAddr; + dirIAddr:inodeAddr; + path, fileNamePtr:PChar; + curPos:integer; + len:integer; + inode:PUbixfsInode; + u:uPtr; + vnid:PVnid; +begin + // XXX ToDo: + // Set appropiate error codes. + // If the "file" is actually a directory, bail on that + // If bailing out, don't forget to reclaim allocated memory + // replace inode^.name with the actual attribute + result := -1; + if (fileName = NIL) or (superBlock = NIL) or (vnids = NIL) then exit; + + // We need to break the path apart into a directory path and filename + len := strlen(filename); + if (len = 0) then exit; + getmem(path, len+1); + strcopy(path, filename); + curPos := len; + while (curPos > 0) and (uInt8ArrayPtr(path)^[curPos] <> ord('/')) do + dec(curPos); + fileNamePtr := path + curPos +1; + + if (curPos = 0) then + begin + dirIAddr := superBlock^.rootDir; + end + else + begin + uInt8ArrayPtr(path)^[curPos] := 0; + dirIAddr := pathToIAddr(path); + end; + // XXX We can't exit because we have allocated memory to clean up + if not (isValidIAddr(dirIAddr)) then exit; // set some error code here + + new(dirTree, open(IAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self)))); + assert(dirTree <> NIL); + fillchar(u, sizeof(u), 0); + if dirTree^.Find(fileNamePtr, u) then + begin + inode := loadBlock(u.iAddr); + if (inode = NIL) then + begin + writeln('error loading inode in unlink()'); + exit; // XXX cannot exit from here either + end; + dirTree^.Delete(@inode^.name, u); + // We know the file exists.. but does anybody have it open? + if vnids^.Find(@inode^.inodeNum, u) then + begin + // file was already open + // mark it for deletion later + inode^.flags := inode^.flags OR INODE_DELETED; + saveBlock(inode^.inodeNum, inode); + end + else + begin + // Tis okay to delete the file + inode^.blocks.size := 0; + shrinkFile(inode); + freeUsedBlocks(inode^.inodeNum); + end; + end + else + writeln('File does not exist'); + dispose(dirTree, done); + + FreeMem(path, len+1); + result := 0; +end; // TUbixFS.vfs_unlink + +function TUbixFS.vfs_write; +begin + result := -1; + if (buffer = NIL) or (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + with fileDescriptors^[handle] do + begin + assert(writeDataStream(iAddr, position, buffer, size) = 0); + position := position + int64(size); + result := size; + end; // with +end; // TUbixFS.vfs_write + + +function TUbixFS.writeDataStream; +var + tmpBlock:uInt8ArrayPtr; + blockRun:TBlockRun; + size, remainder:uInt32; + blockStart:uInt32; + blocksToWrite:uInt32; + bytesToWrite:uInt32; + inodeChanged:boolean; + inode:PUbixFSInode; +begin +// with iAddr do +// writeln('writeDataStream(', AG, ':', start, ':', len, ', ', position, ', ', hex(dword(buf)), ', ', length, ');'); + + result := -1; + if ((buf = NIL) or (superBlock = NIL)) then exit; + inode := loadBlock(iAddr); + tmpBlock := NIL; + + inodeChanged := FALSE; + if (position + length > getFileSpace(inode)) then + begin + extendFile(inode, position+length); +// inode^.blocks.size := position+length; // update the blocks + writeln('after extendfile: size = ', inode^.blocks.size, ' getFileSpace() = ', getFileSpace(inode)); + + end + else + if (position + length > inode^.blocks.size) then + begin + // The above case checks to see if we're writing out past + // the end of the allocated space on the device. If we do, we extend + // out the file. However, if we're just writing past the end of + // the file, we have to update the size. + + inode^.blocks.size := position+length; + // XXX To Do: + // Might want to clear out the blocks we're skipping past, otherwise + // there might be garbage in there + assert(saveBlock(iAddr, inode)); // save the inode + end; + + size := length; + remainder := uInt32(position mod superBlock^.blockSize); + + if (remainder <> 0) then + begin + // the position we've been asked to write to doesn't align to + // a block size. We need to read in the previous block and pad + // the data + blockStart := uInt32(position shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + if (tmpBlock = NIL) then exit; + bytesToWrite := min(size, (superBlock^.blockSize - remainder)); + move(buf^, tmpBlock^[remainder], bytesToWrite); + assert(saveBlock(blockRun, tmpBlock)); + inc(buf, bytesToWrite); + dec(size, bytesToWrite); + position := position + bytesToWrite; + end; // if remainder != 0 + + remainder := size mod superBlock^.blockSize; + dec(size, remainder); + + // Size is currently in bytes. The remainder variable above holds the number + // of remaining bytes (if any) outside full blocks we have to write. + + // convert the size into blocks + size := size shr superBlock^.blockShift; + + while (size > 0) do + begin + blockRun := findBRun(@inode^.blocks, uInt32(position shr superBlock^.blockShift)); + if (blockRun.len = 0) then + begin + printStuff := TRUE; + blockRun := findBRun(@inode^.blocks, uInt32(position shr superBlock^.blockShift)); + end; + assert(blockRun.len > 0); + blocksToWrite := min(blockRun.len, size); + for blocksToWrite := 0 to min(blockRun.len, size)-1 do + begin + assert(saveBlock(blockRun, buf)); + inc(blockRun.start); // This might wrap around? + dec(blockRun.len); // might be unnecessary + dec(size); + inc(buf, superBlock^.blockSize); + end; + end; // while + + if (remainder <> 0) then + begin + // There is less than a block of data left to write. Read in the old block + blockStart := uInt32((position+length-remainder) shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + // obtain a pointer to the block + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + if (tmpBlock = NIL) then exit; + move(buf^, tmpBlock^[0], remainder); + assert(saveBlock(blockRun, tmpBlock)); + end; + result := 0; +end; // TUbixFS.writeDataStream + +destructor TUbixFS.done; +var + searchRec:BTreeSearchRec; + inode:PUbixfsInode; + cacheNode:PCacheNode; + dirtyCount:integer; + u:uPtr; + f:text; + x:integer; +begin + sync(); + // XXX ToDo: + // close all "open" files manually. + if (vnids <> NIL) then dispose(vnids, done); + if (vNodeQueue <> NIL) then dispose(vNodeQueue, done); + if (fileDescriptors <> NIL) then FreeMem(fileDescriptors, maxFDEntry*sizeof(TVNode)); + vnids := NIL; + vNodeQueue := NIL; + fileDescriptors := NIL; + + if (blockTime <> NIL) then dispose(blockTime, done); + if (blockCache <> NIL) then + begin + dirtyCount := 0; + fillchar(u, sizeof(u), 0); +system.assign(f, 'bc1.txt'); +system.rewrite(f); + while (blockCache^.getFirstKey(searchRec)) do + begin + cacheNode := searchRec.value.p; +x := integer(cacheNode^.address); + writeln(f, hex(x)); + + assert(cacheNode <> NIL); + if (cacheNode^.dirty) then inc(dirtyCount); + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + dispose(cacheNode, done); + end; +system.close(f); + if (dirtyCount <> 0) then writeln('Dirty blocks in cache: ', dirtyCount); + dispose(blockCache, done); + end; + if (superBlock <> NIL) then dispose(superBlock); +end; // TUbixFS.done + +procedure TUbixFS.examine; +var + dirTree:PBTree; + searchRec:BTreeSearchRec; + u:uPtr; +begin + new(dirTree, open(iAddrToStr(superBlock^.BAT), new(PUbixBTreeVFS, init(@self)))); + dirTree^.installUserFunctions(compareKeyBlockRun, copyKeyBlockRun, keySizeBlockRun); + dirTree^.printWholeTree('bat.txt'); + dispose(dirTree, done); + writeln('------------- examining FS -----------------'); + write('Opening root dir...'); + new(dirTree, open(IAddrToStr(superblock^.rootDir), new(PUbixBTreeVFS, init(@self)))); + //dirTree^.PrintList('rootDir.txt'); + writeln('done'); + writeln('Root directory has ', dirTree^.getKeyCount(), ' entries'); + dirTree^.getFirstKey(searchRec); + repeat + with searchRec.value.iAddr do + write('value: ', AG, ':', start, ':', len); + writeln(' key: ', pchar(searchRec.key)); + until not dirTree^.findNext(searchRec); + dispose(dirTree, done); + if (blockCache <> NIL) then writeln('Block cache has ', blockCache^.getKeyCount(), ' entries'); + writeln('Space used: ', superBlock^.usedBlocks, '(', superBlock^.numBlocks, ')'); + writeln('------------------ done --------------------'); +end; + +end. \ No newline at end of file diff --git a/ubixfs_test.pas b/ubixfs_test.pas new file mode 100755 index 0000000..965c021 --- /dev/null +++ b/ubixfs_test.pas @@ -0,0 +1,2383 @@ +unit UbixFS; + +interface + +uses device, lists; + +const INODE_NO_FLAG = $00000000; +const INODE_IN_USE = $00000001; + +const ATTR_INODE = $00000004; +const INODE_LOGGED = $00000008; +const INODE_DELETED = $00000010; +const PERMANENT_FLAGS = $0000ffff; +const INODE_NO_CACHE = $00010000; +const INODE_WAS_WRITTEN = $00020000; +const S_IFDIR = $00040000; // POSIX +const NO_TRANSACTION = $00040000; + +const DATASTREAM_DIRECT_BLOCKS = 16; // number of direct blocks in the dataStream + +const UBIXFS_MAGIC_1 = $A0A0A0A; +const UBIXFS_MAGIC_2 = $B0B0B0B; +const UBIXFS_MAGIC_3 = $C0C0C0C; +const UBIXFS_INODE_MAGIC = $3BBE0AD9; + +const UBIXFS_CLEAN = $434C454E; +const UBIXFS_DIRTY = $44495254; + +const SECTOR_SHIFT = 9; +const SECTOR_SIZE = 1 shl SECTOR_SHIFT; + +type + int8 = shortInt; + int16 = smallInt; + int32 = longInt; + + uInt8 = byte; + uInt16 = word; + uInt32 = dWord; +{ Common Integer array definitions } + +type + int8ArrayPtr = ^int8Array; + int8Array = array[ 0..0 ] of int8; + + int16ArrayPtr = ^int16Array; + int16Array = array[ 0..0 ] of int16; + + int32ArrayPtr = ^int32Array; + int32Array = array[ 0..0 ] of int32; + + { Common Unsigned Integer array definitions } + +type + uInt8ArrayPtr = ^uInt8Array; + uInt8Array = array[ 0..0 ] of uInt8; + + uInt16ArrayPtr= ^uInt16Array; + uInt16Array = array[ 0..0 ] of uInt16; + + uInt32ArrayPtr= ^uInt32Array; + uInt32Array = array[ 0..0 ] of uInt32; + +type + + +(* TBlockRun = packed record + AG : int32; + start : uInt16; + len : uInt16; + end; + + inodeAddr = TblockRun; + + uPtr = packed record + case integer of + 0: (i : int32); + 1: (u : uInt32); + 2: (s : single); + 3: (d : double); + 4: (p : pointer); + 5: (offset : int64); + 6: (iAddr : inodeAddr); + end; +*) + PdiskSuperBlock = ^diskSuperBlock; + diskSuperBlock = packed record + name : array[0..31] of char; + magic1 : int32; + + fsByteOrder : int32; + + // blockSize on disk + blockSize : uInt32; + + // number of bits needed to shift a block number to get a byte address + blockShift : uInt32; + + numBlocks : int64; + usedBlocks : int64; + + inodeSize : uInt32; + magic2 : uInt32; + + blocksPerAG : uInt32; + AGShift : uInt32; + numAGs : uInt32; + + flags : uInt32; + + logBlocks : TBlockRun; + logStart : int64; + logEnd : int64; + + magic3 : int32; + BAT : inodeAddr; + rootDir : inodeAddr; + indicies : inodeAddr; + + pad : array[0..371] of byte; + end; // diskSuperBlock + + PDataStream = ^TDataStream; + TDataStream = packed record + direct : array[0..DATASTREAM_DIRECT_BLOCKS-1] of TBlockRun; + maxDirect : int64; + indirect : TBlockRun; + maxIndirect : int64; + doubleIndirect: TBlockrun; + maxDIndirect : int64; + size : int64; + blockCount : uInt32; + end; // TDataStream + + PubixfsInode = ^ubixfsInode; + ubixfsInode = packed record + magic : int32; + inodeNum : inodeAddr; + uid : uInt32; + gid : uInt32; + mode : int32; + flags : int32; + createTime : int64; + modifiedTime : int64; + parent : inodeAddr; + attributes : inodeAddr; + iType : uInt32; + inodeSize : uInt32; + data : uPtr; {this was the etc field in bfs} + blocks : TDataStream; + smallData : array[0..0] of byte; + end; // ubixfsInode + + PVNode = ^TVNode; + TVNode = object + iAddr : inodeAddr; + position : int64; + flags : uInt32; + end; // TVNode + + PVnid = ^TVnid; + TVnid = object + refCount : uInt32; + iAddr : inodeAddr; + mode : uInt32; + end; // TVnid + +(* PFileDescriptor = ^TFileDescriptor; + TFileDescriptor = object + iAddr : inodeAddr; + refCount : uInt32; + flags : uInt32; + handle : uInt32; + end; // fileDescriptor *) + + PUbixFS = ^TUbixFS; + TUbixFS = object + private + dev : PDevice; + superBlock : ^diskSuperBlock; + blockCache : PbTree; + blockTime : PbTree; + vnids : PbTree; + vNodeQueue : PQueue; + fileDescriptors : ^array[0..0] of TVNode; + + maxCacheEntry : uInt32; + maxFDEntry : uInt32; + + procedure assert(x:boolean); + function allocBRun(blockRun:TBlockRun):TBlockRun; + function allocInode(parentIAddr:inodeAddr):PUbixfsInode; + function extendFile(inode:PubixfsInode; newSize:int64):integer; + function findBRun(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunDirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function findBRunDIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun; + function freeUsedBlocks(blockRun:TBlockRun):integer; + function getFileSize(inode:PUbixfsInode):int64; + function getFileSpace(inode:PUbixfsInode):int64; + function iAddrToStr(iAddr:inodeAddr):string; + function isValidIAddr(iAddr:inodeAddr):boolean; + function loadBlock(blockAddr:TBlockRun):pointer; + + function markUsedBlocks(blockRun:TBlockRun):integer; + function nextAG(AG:uInt32):uInt32; + function pathToIAddr(path:PChar):inodeAddr; + + function readDataStream(iAddr:inodeAddr; position:int64; buf:pointer; length:uInt32):integer; + function saveBlock(blockAddr:TBlockRun; buf:pointer):boolean; + procedure setBlockRun(var blockRun:TBlockRun; AG:uInt32; start, len:uInt16); + procedure setIAddr(var inode:inodeAddr; AG:uInt32; start, len:uInt16); + function strToIAddr(s:string):inodeAddr; + function writeDataStream(iAddr:inodeAddr; position:int64; buf:pointer; length:uInt32):integer; + public + constructor init(device:Pdevice); + procedure printSuperBlock; + procedure assign(var F:TFileRec; const _name:string); + procedure reset(var F:TFileRec; blkSize:uInt32); + procedure rewrite(var F:TFileRec; blkSize:uInt32); + procedure sync; + function vfs_fCreate(filename:PChar):integer; + function vfs_fExist(path:PChar):integer; + function vfs_close(handle:integer):integer; + function vfs_format(device:PDevice; fsBlockSize:uInt32; quick:boolean):integer; + function vfs_init:integer; + function vfs_mkdir(path:PChar):integer; + function vfs_read(handle:integer; buffer:pointer; size:uInt32):integer; + function vfs_seek(handle:integer; newPosition:int64):integer; + function vfs_write(handle:integer; buffer:pointer; size:uInt32):integer; + procedure examine; + destructor done; virtual; + end; // TUbixFS + +implementation + +uses strings, dos, math, lists; + +// IMPORTANT NOTE +// You cannot under any circumstance change NUM_EXTENTION_BLOCKS. This +// constant defines the multiple of blocks that a file will be extended +// by, *and* it determines how many blocks are used in each blockRun in +// the Double Indirect dataStream. +// NUM_EXTENSION_BLOCKS determines how much slack space is allocated for +// a file when it is extended. For example, a file with 0 bytes in it +// will take up 0 blocks for the data and 1 block for the inode (ignoring +// space required to hold the directory entry). When the first byte is +// written, extendFile() will allocate a multiple of NUM_EXTENSION_BLOCKS +// to accomodate the amount of data being written out. So, a 1 byte file will +// actually take up NUM_EXTENSION_BLOCKS (plus one for the inode) until the +// file is closed. Then the file will be trimmed and the slack space reclaimed. +// This value is also used in the double-indirect region of the dataStream +// to determine how many blocks each blockRun uses. + +// vv DO NOT CHANGE vv // +const NUM_EXTENSION_BLOCKS = 4; // <--- don't change this +// ^^ DO NOT CHANGE ^^ // + +const NUM_CACHE_ENTRIES = 8192; // default max entries in block cache +const NUM_FD_ENTRIES = 1024; // default max entries in the File Descriptor Table + +type + PCacheNode = ^TCacheNode; + TCacheNode = object + public + address : int64; + lastUsed : int64; + data : pointer; + dataSize : uInt32; + dirty : boolean; + locked : boolean; + constructor init(_address, _lastUsed:int64; _dataSize:uInt32); + destructor done; + end; // TCacheNode + +type + PUbixBTreeVFS = ^TUbixBTreeVFS; + TUbixBTreeVFS = object(TBTreeVFS) + private + fs : PUbixFS; + iAddr : inodeAddr; + position : int64; // limits files to 4GB + public + constructor init(_fs:PUbixFS); + function fClose:boolean; virtual; + function fCreate(const filename:string):boolean; virtual; + function fExist(const filename:string):boolean; virtual; + function fOpen(const filename:string):boolean; virtual; + function fRead(var buf; size:uInt32):boolean; virtual; + function fSeek(offset:uInt32):boolean; virtual; + function fWrite(var buf; size:uInt32):boolean; virtual; + destructor done; virtual; + end; // TUbixBTreeVFS + +function null_file_proc conv arg_based (f: LongInt; buf: pointer; len: ULong; var act: ULong): LongInt; +begin + runerror(999); +end; + +constructor TCacheNode.init; +begin + address := _address; + lastUsed := _lastUsed; + dataSize := _dataSize; + data := NIL; + if (dataSize <> 0) then GetMem(data, dataSize); + dirty := FALSE; + locked := FALSE; +end; // TCacheNode.init + +destructor TCacheNode.done; +begin + if (data <> NIL) and (dataSize <> 0) then FreeMem(data, dataSize); + address := 0; + lastUsed := 0; + data := NIL; + dataSize := 0; + dirty := FALSE; + locked := FALSE; +end; // TCacheNode.done + +constructor TUbixBTreeVFS.init; +begin +// writeln('TUbixBTreeVFS.init()'); + fs := _fs; + fillchar(iAddr, sizeof(iAddr), 0); + position := 0; +end; // TUbixBTreeVFS.init; + +function TUbixBTreeVFS.fClose; +begin + if (fs = NIL) then exit; + position := 0; +end; // TUbixBTreeVFS.fClose + +function TUbixBTreeVFS.fCreate; +begin + // I *think* that this code is only called for the + // B^Tree. Therefore, the file technically exists, but just + // has nothing in it when we call fCreate. + result := fOpen(filename); +end; // TUbixBTreeVFS.fCreate + +function TUbixBTreeVFS.fExist; +var + tmpIAddr:inodeAddr; + tmpInode:PUbixfsInode; +begin + result := FALSE; + assert(fs <> NIL); + if (fs = NIL) then exit; + assert(fs^.superBlock <> NIL); + if (fs^.superBlock = NIL) then exit; + tmpIAddr := fs^.strToIAddr(filename); + if not fs^.isValidIAddr(tmpIAddr) then exit; + + // If we've created an inode, but haven't written any data to it, then + // the B^Tree hasn't written out a superblock, so technically the file doesn't + // exist, and this is a really long run-on sentence. + tmpInode := fs^.loadBlock(tmpIAddr); + + // I'm not sure why, but the compiler cannot properly evaluate (tmpInode^.blocks.size <> 0), + // so we check both the high and low portions of the size to make sure it's not 0. + // result := (tmpInode <> NIL) and ((longLongRec(tmpInode^.blocks.size).lo <> 0) or (longLongRec(tmpInode^.blocks.size).hi <> 0)); + + // However, checking for 0 and then taking the complement of that works .. so we go that way. + result := ((tmpInode <> NIL) and (not (tmpInode^.blocks.size = 0))); +end; // TUbixBTreeVFS.fExist + +function TUbixBTreeVFS.fOpen; +begin + result := FALSE; + if (fs = NIL) then exit; + if (fs^.superBlock = NIL) then exit; + iAddr := fs^.strToIAddr(filename); + position := 0; + result := fs^.isValidIAddr(iAddr); +end; // TUbixBTreeVFS.fOpen + +function TUbixBTreeVFS.fRead; +begin + result := FALSE; + if (fs = NIL) then exit; +// with inode^.inodeNum do +// write('fRead(', AG, ':', start, ':', len, ', '); +// write(position, ', '); +// write('@buf, '); + + result := (fs^.readDataStream(iAddr, position, @buf, size) = 0); + position := position + int64(size); +end; // TUbixBTreeVFS.fRead + +function TUbixBTreeVFS.fSeek; +begin + // uhm.. if the desired offset is beyond the end of the file, + // should we not extend out the file? Or does that occur in writeDataStream ... + position := offset; + result := TRUE; +end; // TUbixBTreeVFS.fSeek + +function TUbixBTreeVFS.fWrite; +begin + result := FALSE; + if (fs = NIL) then exit; +// with inode^.inodeNum do +// write('fWrite(', AG, ':', start, ':', len, ', '); +// write(position, ', '); +// write('@buf, '); + result := (fs^.writeDataStream(iAddr, position, @buf, size) = 0); + position := position + int64(size); + //inc(position, size); +end; // TUbixBTreeVFS.fWrite; + +destructor TUbixBTreeVFS.done; +begin + // XXX: It's possible that we need to save the inode here since + // we possibly changed a directory + fillchar(iAddr, sizeof(iAddr), 0); + fs := NIL; // don't destruct it since we didn't allocate it +end; // TUbixBTreeVFS.done + +constructor TUbixFS.init; +begin + dev := device; + superBlock := NIL; + blockCache := NIL; + blockTime := NIL; + vnids := NIL; + vnodeQueue := NIL; + fileDescriptors := NIL; + maxCacheEntry := NUM_CACHE_ENTRIES; + maxFDEntry := NUM_FD_ENTRIES; +end; // TUbixFS.init + +procedure TUbixFS.assert(x:boolean); +begin + if not x then runerror(1000); +end; // TUbixFS.assert + +{$system} +procedure TUbixFS.assign; +begin + with f do begin + magic := @f; + state := [%file_assigned]; + name := _name; + rd_proc := null_file_proc; + wr_proc := null_file_proc; + check_magic(); + end; // with +end; // TUbixFS.assign + +{$system} +procedure TUbixFS.reset; +begin +// if IO_ApiRet <> 0 then IO_Error(IO_Apiret); +end; // TUbixFS.reset + +procedure TUbixFS.rewrite; +begin + +end; // TUbixFS.rewrite + +function TUbixFS.allocBRun; +var +// inode:PUbixfsInode; + FBL:int8ArrayPtr; // Free Block List array pointer + FBLSize:uInt32; // Free Block List array pointer size + bitOffset:uInt32; + byteOffset:uInt32; + curBlock, baseBlock:uInt32; + freeCount:uInt32; +begin + // Notes: + // 2005-04-27 mji + // allocBRun() will allocate a blockRun that is near to the blockRun + // passed into the function. The AG and Start field of blockRun will be + // the starting point of where to look for free blocks. The len field + // holds how many contiguous blocks we need to allocate. + // We look through the BAT (Block Allocation Table) for a contiguous run that + // fits the requested length. The BAT is a bitfield (used blocks are an + // on (1) bit, free blocks are an off (0) bit) of used/unused logical + // blocks, where the logical block size was set when the device was formatted. + // This shouldn't be confused with the sector size on the device, which + // is always 512 bytes. Block sizes are usually 1k, but can be any power + // of 2 higher than that. 64k is probably the largest anyone would ever + // actually use. Since we only need to load one AG section out of the BAT + // at a time, we only allocate one block worth of memory. + // The disk is broken up into Allocation Groups, where each allocation group + // bitfield fits into a single block. This means that for a device with 1k + // blocks an AG will be 8192 blocks long. Small devices (such as a floppy) + // will only have 1 AG. + // To find a free run, we load a starting section of the BAT out of the + // BAT file. The BAT is technically another file on the volume, but unlike + // normal files the BAT inode is referenced inside the superblock, and cannot + // be touched by a normal user (if the volume is grown later, the BAT can be + // grown as well). + // Once the AG block list is loaded, we scan through looking for a contiguous + // set of free blocks that fits our length requirement. In the event this + // AG doesn't meet that requirement, we move to the next AG. When we hit the + // end of the volume, we go back to the beginning of the volume and continue + // scanning until either we find a free run or we end up back where we began. + // In the latter case, the disk would have to either be very fragmented or + // full. If we succeed, we mark the blocks as used and return the blockRun. + + // XXX To do: + // Might want to clear the blocks we allocated to avoid garbage from being present + + setBlockRun(result, 0, 0, 0); + if (superBlock = NIL) then exit; + if (blockRun.AG > superBlock^.numAGs) then exit; + if (superBlock^.usedBlocks + int64(blockRun.len) > superBlock^.numBlocks) then exit; // should probably give a louder warning + + // we read in one block at a time + FBLSize := superBlock^.blockSize; + GetMem(FBL, FBLSize); + assert(FBL <> NIL); + fillchar(FBL^, FBLSize, 0); // better safe than sorry + + assert(readDataStream(superBlock^.BAT, blockRun.AG shl (superblock^.AGShift shr 3), FBL, FBLSize) = 0); + byteOffset := blockRun.start shr 3; + bitOffset := 7-(blockRun.start and 7); + baseBlock := (blockRun.AG shl superBlock^.AGShift) + blockRun.start; + curBlock := baseBlock; + freeCount := 0; + // an optimization would be to check to see if the byte is -1, thus skipping 8 at a time + // This will need to be implemented (but can be later) + repeat + + inc(curBlock); + + if (FBL^[byteOffset] and (1 shl bitOffset) = 0) then + inc(freeCount) + else + freeCount := 0; + + if (freeCount = blockRun.len) then break; // did we meet our length requirement? + + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + + if (byteOffset = superBlock^.blocksPerAG) then + begin + // We scanned through one Allocation Group of blocks. Move to the next one + inc(blockRun.AG); + + // it's possible that we're wrapping back around to the beginning. + if (blockRun.AG = superBlock^.numAGs) then + begin + // We hit the end of the volume and wrapped back around + blockRun.AG := 0; // Back to the beginning AG + curBlock := 0; // curBlock is from the base of the volume + freeCount := 0; // reset this to 0 + end; + // read in the FBL for the AG we're currently on + assert(readDataStream(superBlock^.BAT, blockRun.AG shl (superblock^.AGShift shr 3), FBL, FBLSize) = 0); + byteOffset := 0; // first byte in this block + bitOffset := 7; // first bit of the first byte + end; // if byteOffset = superBlock^.blocksPerAG + + until curBlock = baseBlock; // repeat until we end up back where we started + + if (freeCount = blockRun.len) then // we found a run + begin + blockRun.AG := (curBlock-blockRun.len) shr superBlock^.AGShift; + blockRun.start := (curBlock-blockRun.len) and (superBlock^.blocksPerAG-1); + markUsedBlocks(blockRun); + result := blockRun; + end + else writeln('Failure in allocBRun()'); + FreeMem(FBL, FBLSize); + +end; // TUbixFS.allocBRun + +function TUbixFS.allocInode; +var + inode:PUbixfsInode; + blockRun:TBlockRun; + +begin + // XXX ToDo: + // need to fill in the rest of the fields in the inode record + + // Notes: + // 2005-04-27 mji + // Inodes take up exactly one logical block on the device. They + // are usually in the same AG as their parent directory. Allocating + // one involves getting some memory for it and finding a free block + // to hold it on the device. + + result := NIL; + if (superBlock = NIL) then exit; + assert(parentIAddr.AG < superBlock^.numAGs); + if (parentIAddr.AG >= superBlock^.numAGs) then exit; // parent addr is outside out block count? + // ask for a single block somewhere around the beginning of the requested AG + setBlockRun(blockRun, parentIAddr.AG, 0, 1); + blockRun := allocBRun(blockRun); + assert(blockRun.len <> 0); + if (blockRun.len = 0) then exit; // this would be bad + // Normally we'd allocate memory for the inode. Instead we let loadBlock() do that + // for us. + inode := loadBlock(blockRun); + + assert(inode <> NIL); + if (inode = NIL) then exit; + fillchar(inode^, superBlock^.inodeSize, 0); + + with inode^ do + begin + magic := UBIXFS_INODE_MAGIC; + + // inodes point to themselves + inodeNum := blockRun; + + uid := 0; // this doesn't mean much here + gid := 0; + // mode := ?? + flags := INODE_NO_FLAG; + // createTime := + // modifiedTime := + + parent := parentIAddr; + setIAddr(attributes, 0, 0, 0); + inodeSize := superBlock^.inodeSize; + // data := 0; + // there's no data yet in the file, so don't + // set blocks to anything (cleared above) + blocks.blockCount := 0; + + end; // with + result := inode; +end; // TUbixFS.allocInode + +function TUbixFS.extendFile; + + function appendToDIndirect(newBlockRun:TBlockRun):boolean; + var + extents:^array[0..0] of TBlockRun; + indirectExtents:^array[0..0] of TBlockRun; + extentsIndex, indirectExtentsIndex:uInt32; + extentsUpperBound:uInt32; + tmpBlockRun:TBlockRun; + begin + // Many apologies for the long variable names. This section is relatively + // complicated, and it wouldn't help to have cryptic variables. + result := FALSE; // failure by default + if (newBlockRun.len mod NUM_EXTENSION_BLOCKS <> 0) then + begin + writeln('Double indirect block run is not a multiple of NUM_EXTENSION_BLOCKS'); + exit; + end; + extentsUpperBound := (superBlock^.blockSize div sizeof(TBlockRun)) - 1; + + if not isValidIAddr(inode^.blocks.doubleIndirect) then + begin + // allocate a double indirect block + inode^.blocks.doubleIndirect := allocBRun(inode^.inodeNum); // allocate a block near the parent inode + + if not isValidIAddr(inode^.blocks.doubleIndirect) then + begin + writeln('Failed to allocate double indirect block'); + exit + end; + // We have a freshly allocated double indirect block. Now we need an indirect block + extents^[0] := allocBRun(inode^.blocks.doubleIndirect); // allocate an indirect block near the dindirect block + if not isValidIAddr(extents^[0]) then + begin + writeln('Failed to allocate indirect block beneath a double indirect block'); + exit + end; + // save out the doubleIndirect block + saveBlock(inode^.blocks.doubleIndirect, extents); + // Because we have a freshly allocated double indirect block, we have no entries + // in it yet. This means that our DIndirect index is 0, and our indirect index is also 0. + extentsIndex := 0; + indirectExtentsIndex := 0; + end + else + begin + // double indirect block exists. Load it + extents := loadBlock(inode^.blocks.doubleIndirect); + assert(extents <> NIL); +//loadBlock(inode^.blocks.doubleIndirect, extents); + // Now scan through (backwards) to find the last entry. + for extentsIndex := extentsUpperBound downto 0 do + if (extents^[extentsIndex].len <> 0) then break; + // extentsIndex points to the entry which holds the indirect block with the last entry. + // load that one + indirectExtents := loadBlock(extents^[extentsIndex]); + assert(indirectExtents <> NIL); +//loadBlock(extents^[extentsIndex], indirectExtents); + // first check the last entry + if (indirectExtents^[extentsUpperBound].len <> 0) then + begin + // this indirect block is full. Allocate a new one + if (extentsIndex = extentsUpperBound) then + begin + // I might as well halt at this point because the user is totally hosed. + writeln('Egad! The double indirect block section is full. Can not write any more to this file!'); + exit; + end; // if doomed + extents^[extentsIndex+1] := allocBRun(extents^[extentsIndex]); + inc(extentsIndex); + indirectExtentsIndex := 0; + saveBlock(inode^.blocks.doubleIndirect, extents); // save, since we changed it + end + else + begin + // now scan forward + for indirectExtentsIndex := 0 to extentsUpperBound do + if (indirectExtents^[indirectExtentsIndex].len = 0) then break; + end; + // extentsIndex points to which indirect block we're on + // indirectExtentsIndex points to the first free entry in that indirect block + end; + + while (newBlockRun.len <> 0) do + begin + tmpBlockRun := newBlockRun; + tmpBlockRun.len := NUM_EXTENSION_BLOCKS; + indirectExtents^[indirectExtentsIndex] := tmpBlockRun; + with newBlockRun do + begin + dec(len, NUM_EXTENSION_BLOCKS); + if ((uInt32(start) + NUM_EXTENSION_BLOCKS) > high(start)) then inc(AG); + inc(start, NUM_EXTENSION_BLOCKS); // Warning! Relies on integer wrap-around (start is 16-bit) + end; // with + + if (indirectExtentsIndex = extentsUpperBound) then + begin + // this indirect block is full. + // Save the current one + saveBlock(extents^[extentsIndex], indirectExtents); + // Allocate a new one + if (extentsIndex = extentsUpperBound) then + begin + // I might as well halt at this point because the user is totally hosed. + writeln('Egad! The double indirect block section is full. Can not write any more to this file!'); + exit; + end; // if doomed + extents^[extentsIndex+1] := allocBRun(extents^[extentsIndex]); // allocate an indirect block near the previous one + inc(extentsIndex); // upadte which index in the double indirect block we're using + indirectExtentsIndex := 0; + indirectExtents := loadBlock(extents^[extentsIndex]); + fillchar(indirectExtents^, superBlock^.blockSize, 0); + saveBlock(inode^.blocks.doubleIndirect, extents); // save, since we changed it + end + else + inc(indirectExtentsIndex); + // update the size the maxDoubleIndirect blocks reference + inode^.blocks.maxDIndirect := inode^.blocks.maxDIndirect + (NUM_EXTENSION_BLOCKS shl superBlock^.blockShift); + end; // while + saveBlock(extents^[extentsIndex], indirectExtents); + end; // local function appendToDIndirect() + + function appendToIndirect(newBlockRun:TBlockRun):boolean; + var + extents:^array[0..0] of TBlockRun; + extentsUpperBound, index:uInt32; + begin + result := FALSE; // failure by default + extentsUpperBound := superBlock^.blockSize div sizeof(TBlockRun)-1; + + if not isValidIAddr(inode^.blocks.indirect) then + begin + // allocate an indirect block + inode^.blocks.indirect := allocBRun(inode^.inodeNum); // alloate a block near the parent inode + if not isValidIAddr(inode^.blocks.indirect) then + begin + writeln('Failed to allocate indirect block'); + exit + end; + extents := loadBlock(inode^.blocks.indirect); + fillchar(extents^, superBlock^.blockSize, 0); + // There might be garbage in the block we just allocated, so write out + // the empty extents pointer to disk. + // On second thought, this is saved below no matter what, so don't + // save it here. + // saveBlock(inode^.blocks.indirect, extents); + end + else + begin + // the indirect block exists, so read it in + extents := loadBlock(inode^.blocks.indirect); + assert(extents <> NIL); +//loadBlock(inode^.blocks.indirect, extents); + end; + // extents[] has the indirect block in it + // We first check to make sure that we're not going to be moving + // to the next section of the dataStream + if (extents^[extentsUpperBound].len <> 0) then + result := appendToDIndirect(newBlockRun) // promote the newBlockRun to the DIndirect section + else + begin + // scan through for the first free slot + for index := 0 to extentsUpperBound do + if extents^[index].len = 0 then break; + extents^[index] := newBlockRun; // index points to the first free slot. Fill it in with the newBlockRun + saveBlock(inode^.blocks.indirect, extents); // save out the indirect block. Could check for failure here + // update the size the maxDoubleIndirect blocks reference + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; // success + end; + end; // local function appendToIndirect() + + function appendToDirect(newBlockRun:TBlockRun):boolean; + var index:uInt32; + begin + result := FALSE; + with inode^, blocks do + begin + if (direct[high(direct)].len <> 0) then + result := appendToIndirect(newBlockRun) + else + begin + for index := 0 to high(direct) do + if (direct[index].len = 0) then break; + direct[index] := newBlockRun; + maxDirect := maxDirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end; // else + end; // with + end; // local function appendToDirect + + function appendToDataStream(newBlockRun:TBlockRun):boolean; + begin + if (inode^.blocks.maxDIndirect <> 0) then result := appendToDIndirect(newBlockRun) + else if (inode^.blocks.maxIndirect <> 0) then result := appendToIndirect(newBlockRun) + else result := appendToDirect(newBlockRun); + end; // local function appendToDataStream + + function continueIndirect(newBlockRun:TBlockRun):boolean; + var + extents:^array[0..0] of TBlockRun; + extentsUpperBound, index:uInt32; + begin + result := FALSE; + extentsUpperBound := superBlock^.blockSize div sizeof(TBlockRun)-1; + extents := loadBlock(inode^.blocks.indirect); + assert(extents <> NIL); + + for index := extentsUpperBound downto 0 do + if (extents^[index].len <> 0) then break; + + assert(extents^[index].len <> 0); // just to be sure + // extents^[index] has the last entry + if (uInt32(extents^[index].len) + newBlockRun.len < 65535) then + begin + inc(extents^[index].len, newBlockRun.len); + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + saveBlock(inode^.blocks.indirect, extents); + result := TRUE; + end + else // oops, we overflowed the length + begin + // need to check to see if we were the last indirect entry. If we were + // then we need to allocate a double indirect block and store it in there + if (index = extentsUpperBound) then + result := appendToDIndirect(newBlockRun) + else + begin + extents^[index+1] := newBlockRun; + inode^.blocks.maxIndirect := inode^.blocks.maxIndirect + (newBlockRun.len shl superBlock^.blockShift); + saveBlock(inode^.blocks.indirect, extents); + result := TRUE; + end; // else + end; // else + + end; // local function continueIndirect + + function continueDirect(newBlockRun:TBlockRun):boolean; + var + index:uInt32; + begin + result := FALSE; + with inode^, blocks do + begin + for index := high(direct) downto low(direct) do + if (direct[index].len <> 0) then break; + + assert(direct[index].len <> 0); // just to be sure + // make sure we don't overflow the length + if (uInt32(direct[index].len) + newBlockRun.len < 65535) then + begin + inc(direct[index].len, newBlockRun.len); // update the last entry + maxDirect := maxDirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end + else // oops, we overflowed the length + begin + // need to check to see if we were the last direct entry. If we were + // then we need to allocate an indirect block and store it in there + if (index = high(direct)) then + result := appendToIndirect(newBlockRun) + else + begin + direct[index+1] := newBlockRun; // store it in the next available entry + maxDirect := maxDirect + (newBlockRun.len shl superBlock^.blockShift); + result := TRUE; + end; + end; // else + end; // with + end; // local function continueDirect + + function continueDataStream(newBlockRun:TBlockRun):boolean; + begin + // The extended blockRun continues a previous run. + + // When extending the file we request an allocation of more blocks + // near to the last blockRun of the file. If the new blocks + // are contiguous to the last blockRun of the file + // then we can simply extend that blockRun. This not only + // prevents fragmentation when possible, it also reduces the + // number of actual blockRuns needed to store the file. + if (inode^.blocks.maxDIndirect <> 0) then result := appendToDIndirect(newBlockRun) + else if (inode^.blocks.maxIndirect <> 0) then result := continueIndirect(newBlockRun) + else result := continueDirect(newBlockRun); + end; // local function continueDataStream + +var + blockRun, exBlockRun:TBlockRun; + blocksToAllocate:uInt32; + +begin // extendFile() + // XXX ToDo: + // Need to verify support for indirect and double-indirect blocks + + // Notes: + // 2005-04-27 mji + // This function is called by writeDataStream() when we're about to write + // past the end of the file. When extending out a file, it is almost never + // extended by the amount written. Instead it is at least 1 block, and usually + // more (in our case: 4, as mentioned below). Currently we pass in the number + // of blocks to extend the file by. This is problematic for a few reasons. + // The first is that the code to figure out how much to extend the file by + // would have to be in writeDataStream(). That's not necessarily a problem, + // except that the amount to be extended by depends on many factors, some + // including whether we're writing out more than the default small block + // extension size (currently is 4) or if we're in double indirect blocks, + // which extends out by NUM_DINDIRECT_BLOCKS (also currently 4). + // The second problem is that we save the inode here -- and also in + // writeDataStream() -- because the new size of the file is updated in wDS() + // instead of here. + // The third problem is when you seek past the end of the file and then + // write. Space should be allocated for all the space between the old + // EOF and the new write position. This is common behaviour for most + // filesystems, and should probably be emulated here as well. + // The proper solution is to pass in the new size and let this function + // work out exactly how much space on disk it is really going to take. + + result := -1; // failure by default + // basic sanity checks + if (inode = NIL) or (superBlock = NIL) then exit; + + // if the file size is 0 then this is the first extension. + if (inode^.blocks.size = 0) then // first extension + begin + // A file's data is stored in the next allocation group from it's parent + // inode. This will help distribute the data across the disk. + + // Find out how many blocks the new size will take up. Do this by rounding + // up the size to the nearest block. Then we round that up to the nearest + // NUM_DIRECT_BLOCKS multiple. + blocksToAllocate := uInt32(((newSize+(superBlock^.blockSize-1)) shr superBlock^.blockShift)); + blocksToAllocate := (blocksToAllocate + (NUM_EXTENSION_BLOCKS-1)) and not (NUM_EXTENSION_BLOCKS-1); + setBlockRun(exBlockRun, nextAG(inode^.inodeNum.AG), 0, blocksToAllocate); + exBlockRun := allocBRun(exBlockRun); // try to allocate a blockrun + assert(exBlockRun.len <> 0); + if (exBlockRun.len = 0) then exit; // erp. Couldn't extend the file + inode^.blocks.direct[0] := exBlockRun; // Set the first direct run + // The space allocated for the file can and may be different than the + // actual size. Set the maxDirect to the size (in bytes) of how much space + // we allocated + inode^.blocks.maxDirect := blocksToAllocate shl superBlock^.blockShift; + end // size = 0 + else + begin + // the size of the file wasn't 0 (handled above). We need to extend the + // file out. Find where the last blocks of the file are + + // Note: + // 2005-04-28 mji + // We're going to have a potential problem here. When we check to see how many blocks we + // want to extend by, we don't know for sure if we're actually going to end up in the + // dataStream section we were assuming. For example, we may have no more room in the + // indirect block section of dataStream and thus will have to store the entry in the double + // indirect area. If the constants for each section are different, then we may be requesting the + // wrong amount. This is *only* a problem if any of the constants are different. Therefore + // the solution is to pick some arbitrary extension amount (probably 4 or 8 blocks) and stick + // with that. If we pick the amount that will end up in the double indirect area (currently 4 + // but I'm considering 8) then we can easily break apart our blockRun if we're writing it + // to the double indirect section. + // IMPORTANT NOTE + // Once this value is chosen it will be set in stone since we will break everything by + // changing it. Math done for retrieving the blockRun from the double indirect region will + // rely on this value, and it can never be changed. BeFS uses 4 blocks for the double + // indirect section, and that seems fine with me. The largest file using contiguous blockRuns + // will be 38GB. I am, however, pondering 8 blocks instead. Remember, extensions are done + // as a multiple of this value. + // 2005-04-28 mji + // I decided to use a single constant, NUM_EXTENSION_BLOCKS, and it is set to 4. + blocksToAllocate := uInt32((newSize+(superBlock^.blockSize-1)) shr superBlock^.blockShift); + // blocksToAllocate is now rounded up to the nearest block of how much space we need. + blocksToAllocate := (blocksToAllocate + (NUM_EXTENSION_BLOCKS-1)) and not (NUM_EXTENSION_BLOCKS-1); + // blocksToAllocate is now rounded up to the nearest multiple of NUM_EXTENSION_BLOCKS + // now subtract off how many blocks we already have + dec(blocksToAllocate, inode^.blocks.blockCount); + + // XXX + // I don't think this is right. We need to find the last block returned by + // getFileSpace(), not the size. Something to ponder/look into/test + //blockRun := findBRun(@inode^.blocks, inode^.blocks.size shr superBlock^.blockShift); + assert(inode^.blocks.blockCount <> 0); // if blockCount is 0, we're in big trouble + blockRun := findBRun(@inode^.blocks, inode^.blocks.blockCount-1); + assert(blockRun.len <> 0); + if (blockRun.len = 0) then exit; // This would be odd + exBlockRun := blockRun; // copy the last run to exBlockRun + exBlockRun.len := blocksToAllocate; // set the requested number of blocks +writeln('numBlocks[5]: ', blocksToAllocate); +with exBlockRun do +writeln('exBlockRun before allocBRun() = ', ag, ':', start, ':', len); + + exBlockRun := allocBRun(exBlockRun); // allocate a block run near the previous one +with exBlockRun do +writeln('exBlockRun after allocBRun() = ', ag, ':', start, ':', len); + assert(exBlockRun.len <> 0); + if (exBlockRun.len = 0) then exit; // should probably yell louder about this + + // blockRun points to the last blockRun structure associated with this inode + if (exBlockRun.start = blockRun.start+1) and (exBlockRun.AG = blockRun.AG) then + continueDataStream(exBlockRun) + else + appendToDataStream(exBlockRun); + end; // else size <> 0 + inode^.blocks.size := newSize; // Now we update the size here and not in writeDataStream() + inc(inode^.blocks.blockCount, blocksToAllocate); // update the blockCount in the inode dataStream + // no matter where we stored the extended blockRun, we have updated the inode + + assert(saveBlock(inode^.inodeNum, inode)); // 0 result means success + result := 0; // no error +end; // TUbixFS.extendFile + +function TUbixFS.findBRun; +var + position:int64; +begin + setBlockRun(result, 0, 0, 0); + if (dataStream = NIL) then exit; + if (blockNum >= dataStream^.blockCount) then + begin + writeln('Requested block outside the block count in the dataStream'); + exit + end; + position := int64(blockNum) shl superBlock^.blockShift; + if (position < dataStream^.maxDirect) then + result := findBRunDirect(dataStream, blockNum) + else if (position < dataStream^.maxIndirect) then + result := findBRunIndirect(dataStream, blockNum) + else if (position < dataStream^.maxDIndirect) then + result := findBRunDIndirect(dataStream, blockNum) + else writeln('Block position is not mapped by dataStream'); +end; // TUbixFS.findBRun + +function TUbixFS.findBRunDirect; +var + sum, i, offset:uInt32; +begin + // set the result to a nil blockrun + setBlockRun(result, 0, 0, 0); + + sum := 0; + with dataStream^ do + for i := low(direct) to high(direct) do + with direct[i] do + begin + if (blockNum < sum + len) then // found it + begin + offset := blockNum - sum; + setBlockRun(result, AG, start + offset, len - offset); + exit + end; + inc(sum, len); + end; // for/with +end; // TUbixFS.findBRunDirect + +function TUbixFS.findBRunIndirect; +var + extents:^array[0..0] of TBlockRun; + sum, i, offset:uInt32; +begin + // NOTE: Basic error checking on whether superblock <> NIL isn't done here. + setBlockRun(result, 0, 0, 0); + + // load the indirect block + extents := loadBlock(dataStream^.indirect); + assert(extents <> NIL); + + // We either need to adjust sum forward or blockNum back. + // sum := (dataStream^.maxDirect+superBlock^.blockSize-1) shr superBlock^.blockShift; + + // We need to decrease blockNum by the amount of blocks the direct portion took up. + // This can be calculated by dividing the maxDirect byte size by the block size. + + blockNum := blockNum - dword(dataStream^.maxDirect shr superBlock^.blockShift); + sum := 0; + + for i := 0 to (superBlock^.blockSize div sizeof(TBlockRun))-1 do + with extents^[i] do + begin + if (blockNum < sum + len) then // found it + begin + offset := blockNum - sum; + setBlockRun(result, AG, start + offset, len - offset); + break + end; + inc(sum, len); + end; // for/with + +end; // TUbixFS.findBRunIndirect + +function TUbixFS.findBRunDIndirect; +var + extents:^array[0..0] of TBlockRun; + sum, i, offset:uInt32; +begin + // NOTE: Basic error checking on whether superblock <> NIL isn't done here. + setBlockRun(result, 0, 0, 0); + + extents := loadBlock(dataStream^.doubleIndirect); + assert(extents <> NIL); + // Now that we've loaded the double indirect block, we need to + // find the indirect block that contains the block run we're looking for. + // This is done with some clever shifting + + // first adjust the blockNum, taking into account how many blocks are used + // by the direct and indirect sections + + blockNum := blockNum - dword(dataStream^.maxIndirect shr superBlock^.blockShift); + + // Each indirect block holds NUM_EXTENSION_BLOCKS*(superBlock^.blockSize div sizeof(TBlockRun)) blocks + // e.g. + // NUM_EXTENSION_BLOCKS = 4 + // blockSize = 1024 + // sizeof(TBlockRun) = 8 + // blocks referenced per indirect block = 4 * (1024 / 8) === 512 + // indirect block index = (block we're looking for) / (blocks referenced per indirect block) + // blockrun index in indirect block = (block we're looking for) % (blocks referenced per indirect block) + + i := blockNum div (NUM_EXTENSION_BLOCKS * (superBlock^.blockSize div sizeof(TBlockRun))); + // i should now hold which entry in the double indirect block we need to load + assert(i < (superBlock^.blockSize div sizeof(TBlockRun))); + + // load the proper indirect block beneath the double indirect block + extents := loadBlock(extents^[i]); + assert(extents <> NIL); + + // now seek into this indirect block by taking the modulus of the number + // of blocks referenced by an indirect block + i := blockNum mod (NUM_EXTENSION_BLOCKS * (superBlock^.blockSize div sizeof(TBlockRun))); + with extents^[i] do + begin + // extents^[i] is a block extent inside an indirect block which + // was referenced by the double indirect block. Now we need to see + // which block inside this extent we wanted. We do that by taking the + // modulus of the requested block number by the number of blocks + // referenced in each blockrun. BeFS used 4 per; and as of writing + // this comment that is what I'm using. + offset := blockNum and (NUM_EXTENSION_BLOCKS-1); + setBlockRun(result, AG, start + offset, len - offset); + end; // with + +end; // TUbixFS.findBRunDIndirect + +function TUbixFS.freeUsedBlocks; +var + FBL:int8ArrayPtr; + FBLSize:uInt32; + startByte, endByte:uInt32; + bitCount, bitOffset:uInt32; + startBlock:uInt32; + byteOffset:uInt32; +begin + // Notes: + // 2005-04-27 mji + // freeUsedBlocks() will clear bits in the BAT that are associated + // with the blockRun passed in. To do this we load only the portion + // of the BAT we're going to modify. (As a side note, readDataStream() + // will load a minimum of one block anyway, so this may not really be + // much of an optimization). + + result := -1; + // basic sanity checks + if ((superBlock = NIL) or (blockRun.len = 0)) then exit; + + // Compute how much of the BAT we have to read in. + with blockRun, superBlock^ do + begin + startBlock := (AG shl AGShift) + start; + startByte := startBlock shr 3; // divide by 8 since it's a bitfield + endByte := (startBlock + len) shr 3; // divide by 8 since it's a bitfield + FBLSize := endByte - startByte + 1; + + GetMem(FBL, FBLSize); + assert(readDataStream(BAT, startByte, FBL, FBLSize) = 0); + bitOffset := 7-(startBlock and 7); + byteOffset := 0; + + for bitCount := 1 to len do + begin + FBL^[byteOffset] := FBL^[byteOffset] and not (1 shl bitOffset); + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + end; // for bitCount + // save back out the modified block + assert(writeDataStream(BAT, startByte, FBL, FBLSize) = 0); + + FreeMem(FBL, FBLSize); + usedBlocks := usedBlocks - len; + end; // with + + result := 0; +end; // TUbixFS.freeUsedBlocks + +function TUbixFS.getFileSize; +begin + // XXX ToDo: + // Fix to support sizes > 4GB + result := 0; + if (inode = NIL) then exit; + result := inode^.blocks.size; +end; // TUbixFS.getFileSize + +function TUbixFS.getFileSpace; +begin + // getFileSpace() will return the size of the file's allocated space + result := 0; + if (inode = NIL) or (superBlock = NIL) then exit; + result := inode^.blocks.blockCount shl superBlock^.blockShift; +end; // TUbixFS.getFileSpace + +function TUbixFS.iAddrToStr; +var s:string; +begin + // Note: this won't work in C/C++ because strings are really null + // terminated character arrays. So, the B^Tree won't hold a string + // with nulls in it properly + length(s) := sizeof(iAddr); + move(iAddr, s[1], sizeof(iAddr)); + result := s; +end; // TUbixFS.iAddrToStr; + +function getRDTSCTime:int64; +var x:int64; +begin + asm + rdtsc + mov dword ptr x, eax + mov dword ptr x+4, edx + end; + result := x; +end; + +function TUbixFS.loadBlock; +var + blockAddress:int64; + cacheNode:PCacheNode; + u:uPtr; + searchRec:BTreeSearchRec; +begin + result := NIL; // failure by default; + // 2005-04-29 mji + // I'm not really sure how much error checking to put in here. Chances are that + // if we're calling this function everything is fine. For now, with such limited + // use I'll keep it pretty light. I think that this will eventually be used for + // dealing with the block cache, so more checking might be required later. + if (dev = NIL) or (superBlock = NIL) or (blockCache = NIL) then exit; + + with blockAddr, superBlock^ do + blockAddress := (AG shl AGShift) + start; + + fillchar(u, sizeof(u), 0); // just to be safe + cacheNode := NIL; + // XXX ToDo + // Fix this so that if there is no cache, we only have one pointer + // Check for a full cache + + if (blockCache^.Find(@blockAddress, u)) then + begin + // block was in the cache. + assert(u.p <> NIL); + cacheNode := u.p; + assert(cacheNode^.data <> NIL); + // If we're going to honour the locked flag in the cacheNode, then + // we should put a mutex here + + // Now we need to update the blockTime. We do this by first deleting + // the cacheNode out of the blockTime cache. + blockTime^.Delete(@cacheNode^.lastUsed, u); // Delete old one + cacheNode^.lastUsed := getRDTSCTime(); // Get the new timestamp + blockTime^.Insert(@cacheNode^.lastUsed, u); // Insert with new time + result := cacheNode^.data; // return the data pointer + end + else + begin + // Requested block isn't in cache + + // XXX ToDo: + // Check to see if we've hit the maximum cache entry limit. If so, we + // need to drop off the least recently used (look for the first key + // in blockTime) and possibly save it to disk if it has been modified. + + if (blockCache^.getKeyCount() = maxCacheEntry) then + begin + blockTime^.getFirstKey(searchRec); // least recently used block + cacheNode := searchRec.value.p; // get the pointer from the value + assert(cacheNode <> NIL); + // I suppose we could write a syncBlock() function, since this + // code is also duplicated in sync(). + if (cacheNode^.dirty) then + begin + with cacheNode^ do + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + cacheNode^.dirty := FALSE; + end; // if dirty + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + blockTime^.Delete(@cacheNode^.lastUsed, u); + // We've deleted the cacheNode out of both the blockCache and + // the blockTime trees. We're going to re-use the cacheNode. + cacheNode^.address := blockAddress; + cacheNode^.lastUsed := getRDTSCTime(); + // No need to update data, dataSize, or dirty. We re-use the first two, + // and dirty will be set to FALSE above if necessary. Might need to + // reset locked if we're using that. + end + else + new(cacheNode, init(blockAddress, getRDTSCTime(), superBlock^.blockSize)); + + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + u.p := cacheNode; + if not (blockCache^.Insert(@blockAddress, u)) then + writeln('Error inserting into blockCache'); + if not (blockTime^.Insert(@cacheNode^.lastUsed, u)) then + writeln('Error inserting into blockTime'); + // now we need to fill the data from the device + with superBlock^ do + dev^.read(cacheNode^.data, + blockAddress * (blockSize shr SECTOR_SHIFT), + blockSize shr SECTOR_SHIFT + ); // dev^.read + result := cacheNode^.data; + end; +end; // TUbixFS.loadBlock + +function TUbixFS.markUsedBlocks; +var + FBL:uInt8ArrayPtr; + FBLSize:uInt32; + startByte, endByte:uInt32; + bitCount, bitOffset:uInt32; + startBlock:uInt32; + byteOffset:uInt32; +begin + + result := -1; + // basic sanity checks + if ((superBlock = NIL) or (blockRun.len = 0)) then exit; + +with blockRun do writeln('---- marking used blocks: ', AG, ':', start, ':', len); + + // Compute how much of the BAT we have to read in. + with blockRun, superBlock^ do + begin + startBlock := (AG shl AGShift) + start; + startByte := startBlock shr 3; + endByte := (startBlock + len) shr 3; + FBLSize := endByte - startByte + 1; + GetMem(FBL, FBLSize); + fillchar(FBL^, FBLSize, 0); // better safe than sorry + assert(readDataStream(BAT, startByte, FBL, FBLSize) = 0); + + bitOffset := 7-(startBlock and 7); + byteOffset := 0; + for bitCount := 1 to len do + begin + FBL^[byteOffset] := FBL^[byteOffset] or (1 shl bitOffset); + if (bitOffset = 0) then + begin + bitOffset := 7; + inc(byteOffset); + end + else dec(bitOffset); + end; // for bitCount + assert(writeDataStream(BAT, startByte, FBL, FBLSize) = 0); + + FreeMem(FBL, FBLSize); + + usedBlocks := usedBlocks + len; + end; // with + result := 0; +end; // TUbixFS.markUsedBlocks + +function TUbixFS.nextAG; +begin + // there's no error we can throw from here if superblock is NIL, + // so don't bother to check. Just hope whomever calls this + // function has done some basic sanity checks + result := (AG+1) mod superBlock^.numAGs +end; // TUbixFS.nextAG + +function TUbixFS.pathToIAddr; + + function recursePath(path:PChar; len:integer; iAddr:inodeAddr):inodeAddr; + var + dirTree:PBTree; + u:uPtr; + nameSize:integer; + pfs:PUbixBTreeVFS; + begin + // if the string length is 0 then we have found the inode in the previous + // recurse, so return what we found. + if (len = 0) then + begin + result := iAddr; + exit; + end + else + setIAddr(result, 0, 0, 0); + // hm. If we're looking for something like ``/.'' then the ``/'' is stripped + // off in the main call leaving only ``.'', which means that this malformed + // path check would trigger. Same with /foo/bar if bar is a dir. + //if uInt8ArrayPtr(path)^[0] = ord('/') then exit; // malformed path + if not (isValidIAddr(iAddr)) then exit; // sanity check + nameSize := 0; + while (len > 0) and (uInt8ArrayPtr(path)^[nameSize] <> ord('/')) do + begin + inc(nameSize); + dec(len); + end; + uInt8ArrayPtr(path)^[nameSize+1] := 0; + if (len <> 0) then dec(len); + new(dirTree, open(IAddrToStr(iAddr), new(PUbixBTreeVFS, init(@self)))); + if (dirTree = NIL) then exit; // hm.. what happens to the PUbixBTreeVFS is this is NIL? + // note that the dirTree is a tree of BT_PCHAR, and searches are case sensitive. + // if the tree doesn't exist, Find() should return false. + if dirTree^.Find(path, u) then result := recursePath(path + nameSize + 1, len, u.iAddr); + dispose(dirTree, done); + end; // recursePath + +var len:integer; +begin + setIAddr(result, 0, 0, 0); + + if (path = NIL) or (superBlock = NIL) then exit; + + len := strlen(path); + if (len = 0) then exit; // empty string + if uInt8ArrayPtr(path)^[0] <> ord('/') then exit; // malformed path + + result := recursePath(path+1, len-1, superBlock^.rootDir); + +end; // TUbixFS.pathToIAddr + +procedure TUbixFS.printSuperBlock; +begin + if (superBlock = NIL) then exit; + with superBlock^ do + begin + writeln('superBlock^.name........... ', name); + writeln('superBlock^.magic1......... ', hex(magic1)); + writeln('superBlock^.fsByteOrder.... ', fsByteOrder); + writeln('superBlock^.blockSize...... ', blockSize); + writeln('superBlock^.blockShift..... ', blockShift); + writeln('superBlock^.numBlocks...... ', numBlocks); + writeln('superBlock^.usedBlocks..... ', usedBlocks); + writeln('superBlock^.magic2......... ', hex(magic2)); + writeln('superBlock^.blocksPerAG.... ', blocksPerAG); + writeln('superBlock^.AGShift........ ', AGShift); + writeln('superBlock^.numAGs......... ', numAGs); + writeln('superBlock^.flags.......... ', hex(flags)); + writeln('superBlock^.magic3......... ', hex(magic3)); + with superBlock^.BAT do + writeln('superBlock^.BAT............ ', AG, ':', start, ':', len); + with superBlock^.rootDir do + writeln('superBlock^.rootDir........ ', AG, ':', start, ':', len); + end; +end; // TUbixFS.printSuperBlock + +function TUbixFS.saveBlock; +var + blockAddress:int64; + cacheNode:PCacheNode; + u:uPtr; + searchRec:BTreeSearchRec; +begin + + result := FALSE; // failure by default; + if (blockCache = NIL) or (blockTime = NIL) or (dev = NIL) or (superBlock = NIL) then exit; + with blockAddr, superBlock^ do + blockAddress := (AG shl AGShift) + start; + cacheNode := NIL; + fillchar(u, sizeof(u), 0); // best be safe + // first check to see if the blockAddress is in the cache + if (blockCache^.Find(@blockAddress, u)) then + begin + // block was in cache + cacheNode := u.p; + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + if (cacheNode^.data <> buf) then + begin + // The buffer with the data we want to save was not the same as the one + // in the cache. This means that it was not obtained with loadBlock(). + // We need to copy that pointer to our block. + move(buf^, cacheNode^.data^, cacheNode^.dataSize); + end; + // Now we need to update the blockTime. We do this by first deleting + // the cacheNode out of the blockTime cache. + blockTime^.Delete(@cacheNode^.lastUsed, u); // Delete old one + cacheNode^.lastUsed := getRDTSCTime(); // Get the new timestamp + blockTime^.Insert(@cacheNode^.lastUsed, u); // Insert with new time + end + else + begin + // block was not in cache + if (blockCache^.getKeyCount() = maxCacheEntry) then + begin + blockTime^.getFirstKey(searchRec); + cacheNode := searchRec.value.p; + assert(cacheNode <> NIL); + // I suppose we could write a syncBlock() function, since this + // code is also duplicated in sync(). + if (cacheNode^.dirty) then + begin + with cacheNode^ do + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + cacheNode^.dirty := FALSE; + end; // if dirty + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + blockTime^.Delete(@cacheNode^.lastUsed, u); + // We've deleted the cacheNode out of both the blockCache and + // the blockTime trees. We're going to re-use the cacheNode. + cacheNode^.address := blockAddress; + cacheNode^.lastUsed := getRDTSCTime(); + // No need to update data, dataSize, or dirty. We re-use the first two, + // and dirty will be set to FALSE above if necessary. Might need to + // reset locked if we're using that. + end + else + new(cacheNode, init(blockAddress, getRDTSCTime(), superBlock^.blockSize)); + + assert(cacheNode <> NIL); + assert(cacheNode^.data <> NIL); + move(buf^, cacheNode^.data^, cacheNode^.dataSize); + u.p := cacheNode; + if not (blockCache^.Insert(@blockAddress, u)) then + writeln('Error inserting into blockCache'); + if not (blockTime^.Insert(@cacheNode^.lastUsed, u)) then + writeln('Error inserting into blockTime'); + end; + cacheNode^.dirty := TRUE; // indicate the block has been modified + result := TRUE; +end; // TUbixFS.saveBlock + +function TUbixFS.isValidIAddr; +begin + result := (iAddr.len <> 0) and (iAddr.AG < superBlock^.numAGs); +end; // TUbixFS.isValidIAddr + +function TUbixFS.readDataStream; +var + tmpBlock:uInt8ArrayPtr; + blockRun:TBlockRun; + size, remainder:uInt32; + blockStart:uInt32; + blocksToRead:uInt32; + bytesToRead:uInt32; + inode:PUbixFSInode; +begin + + result := -1; + if ((buf = NIL) or (superBlock = NIL)) then exit; + inode := loadBlock(iAddr); + tmpBlock := NIL; + size := length; + remainder := uInt32(position mod superBlock^.blockSize); + + if (remainder <> 0) then + begin + // the position we've been asked to read from doesn't align to + // a block size. We need to read in the previous block and pad + // the data + blockStart := uInt32(position shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + bytesToRead := min(size, (superBlock^.blockSize - remainder)); + move(tmpBlock^[remainder], buf^, bytesToRead); + inc(buf, bytesToRead); + dec(size, bytesToRead); + position := position + bytesToRead; + end; // if remainder != 0 + + // If the blockSize is always a power of 2, then you can eliminate the MOD and use + // an AND operation instead, e.g. remainder := size and (superBlock^.blockSize-1) + remainder := size mod superBlock^.blockSize; + dec(size, remainder); + + // Size is currently in bytes. The remainder variable above holds the number + // of remaining bytes (if any) outside full blocks we have to write. + + // convert the size into blocks + size := size shr superBlock^.blockShift; + + while (size > 0) do + begin + blockRun := findBRun(@inode^.blocks, dword(position shr superBlock^.blockShift)); + assert(blockRun.len > 0); + for blocksToRead := 0 to min(blockRun.len, size)-1 do + begin + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + move(tmpBlock^, buf^, superBlock^.blockSize); + dec(size); + inc(buf, superBlock^.blockSize); + inc(blockRun.start); // This might wrap around? + dec(blockRun.len); // might be unnecessary + end; // for + end; // while + + if (remainder <> 0) then + begin + // There is less than a block of data left to read. + blockStart := dword((position+length-remainder) shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + move(tmpBlock^, buf^, remainder); + end; + + result := 0; +end; // TUbixFS.readDataStream + +procedure TUbixFS.setBlockRun; +begin + blockRun.AG := AG; + blockRun.start := start; + blockRun.len := len; +end; // TUbixFS.setBlockRun + +procedure TUbixFS.setIAddr; +begin + inode.AG := AG; + inode.start := start; + inode.len := len; +end; // TUbixFS.setIAddr + +function TUbixFS.strToIAddr; +begin + // Note: this won't work in C/C++ because strings are really null + // terminated character arrays. So, the B^Tree won't hold a string + // with nulls in it properly + // begin fiendishly clever hack + move(s[1], result, sizeof(inodeAddr)); + // end fiendishly clever hack +end; // TUbixFS.strToIAddr + +procedure TUbixFS.sync; +var + cacheNode:PCacheNode; + searchRec:BTreeSearchRec; +begin + // XXX ToDo: + // Need to mark the superblock as clean. + + if (blockCache = NIL) or (dev = NIL) then exit; + // We sync out the blocks in ascending order from the blockCache. + // To do this we examine each entry to see if the block is dirty. + // If it is, we write it out to the device and set dirty to FALSE. + if (blockCache^.getFirstKey(searchRec)) then + repeat + cacheNode := searchRec.value.p; + assert(cacheNode <> NIL); + if (cacheNode^.dirty) then + begin + with cacheNode^ do + dev^.write(data, + address * (dataSize shr SECTOR_SHIFT), + dataSize shr SECTOR_SHIFT + ); // dev^.write + cacheNode^.dirty := FALSE; + end; + until not blockCache^.FindNext(searchRec); + // Also write out the superblock. + dev^.write(superBlock, 1, 1); +end; // TUbixFS.sync + +function TUbixFS.vfs_close; +var + u:uPtr; + vnid:PVnid; +begin + result := -1; + if (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + if not (vnids^.Find(@fileDescriptors^[handle].iAddr, u)) then writeln('Could not find iAddr in vnids'); + vnid := u.p; + if (vnid = NIL) then + begin + writeln('vnid is NIL'); + exit; + end; + + with vnid^ do + begin + dec(refCount); + if (refCount = 0) then + begin + // refCount has fallen to 0. If the file is deleted we should take care of it here + vnids^.Delete(@fileDescriptors^[handle].iAddr, u); + end; + end; + if not vNodeQueue^.enqueue(handle) then writeln('Error enqueuing handle back into vNodeQueue'); + result := 0; +end; // TUbixFS.vfs_close + +function TUbixFS.vfs_fCreate; +var + inode:PUbixFSInode; + dirTree:PBTree; + dirIAddr, fileIAddr:inodeAddr; + filedes:int64; + u:uPtr; + searchRec:BTreeSearchRec; + len : integer; + path:PChar; + filenamePtr:PChar; + curPos:integer; + vnid:PVnid; + handle:integer; +begin + result := -1; + if (filename = NIL) or (superBlock = NIL) then exit; + // we need to split the filename into it's component path and filename pieces. + // From there we need to see if the file exists. If it does then I guess we + // should truncate the file and load the inode. Otherwise allocate a new inode + len := strlen(filename); + if (len = 0) then exit; + if uInt8ArrayPtr(filename)^[0] <> ord('/') then exit; // malformed path + GetMem(path, len+1); + strCopy(path, filename); + curPos := len; + + setIAddr(dirIAddr, 0, 0, 0); // zero out the inode address for the directory + setIAddr(fileIAddr, 0, 0, 0); // zero out the inode address for the file + + while (curPos > 0) and (uInt8ArrayPtr(path)^[curPos] <> ord('/')) do dec(curPos); + fileNamePtr := path + curPos + 1; + if (curPos = 0) then + begin + // file is in the root dir + writeln('path: /'); + writeln('filename: ', fileNamePtr); + dirIAddr := pathToIAddr('/'); + end + else + begin + uInt8ArrayPtr(path)^[curPos] := 0; // null out the separator + writeln('Path: ', path); + writeln('Filename: ', fileNamePtr); + dirIAddr := pathToIAddr(path); + end; + + with dirIAddr do + writeln('iaddr to path: ', AG, ':', start, ':', len); + assert(isValidIAddr(dirIAddr)); + if not isValidIAddr(dirIAddr) then exit; + // should probably check to make sure that we're opening a directory and not a file + new(dirTree, open(IAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self)))); + if (dirTree = NIL) then exit; + // We need to ascertain if the file is existing or new. + // If the file exists, we should truncate the file + // If the file is new, we need to create a new inode and write the entry to the directory + // Then we need to allocate a file descriptor and store it somewhere. + if dirTree^.Find(fileNamePtr, u) then + begin + writeln(fileNamePtr, ' already exists'); + end + else + begin + writeln('creating new file: ', fileNamePtr); + writeln('allocating new inode'); + inode := allocInode(dirIAddr); // alloc a new inode (pass in the parent iaddr) + assert(inode <> NIL); // make sure it isn't nil + if (inode = NIL) then exit; + saveBlock(inode^.inodeNum, inode); // save the new inode + u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr + // now add the file to the directory + writeln('Inserting ', fileNamePtr, ' into directory returned ', dirTree^.Insert(fileNamePtr, u)); + end; + dispose(dirTree, done); + FreeMem(path, len+1); + + // This file doesn't have an associated vnid. Create a new one. + new(vnid); + vnid^.refCount := 1; + vnid^.iAddr := inode^.inodeNum; + vnid^.mode := 0; // XXX ? + u.p := vnid; + if not (vnids^.insert(@inode^.inodeNum, u)) then writeln('Could not insert into vnids'); + + if not vNodeQueue^.dequeue(handle) then + begin + writeln('Out of file descriptors!'); + exit; + end; + + with fileDescriptors^[handle] do + begin + iAddr := inode^.inodeNum; + position := 0; + flags := 0; // XXX ? + end; + result := handle; +end; // TUbixFS.vfs_fCreate + +function TUbixFS.vfs_fExist; +begin + // XXX ToDo: + // find out why fExist('/./.') fails + // fExist('/.') no longer fails + + result := -1; + if ((superBlock = NIL) or (path = NIL)) then exit; + if (not isValidIAddr(superBlock^.rootDir)) then exit; + if isValidIAddr(pathToIAddr(path)) then result := 0; +end; // TUbixFS.vfs_fExist + +function TUbixFS.vfs_format; +var + sector:array[0..511] of byte; + fs:PUbixFS; + logicalBlocks, fsBATSize, fsBATBlocks:uInt32; + sb:PdiskSuperBlock; + inode:PubixfsInode; + i, fsBlockShift, lBlockShift, nodeSize, keyLength:integer; + blockRun:TBlockRun; + remainderBlocks:uInt32; + blockBit:shortint; +begin + result := -1; + if (device = NIL) then exit; + if (fsBlockSize < 1024) then + begin + writeln('Blocksize must be >= 1024'); + exit; + end; + // zero out the sector + fillchar(sector, sizeof(sector), 0); + + writeln('device^.sectors: ', device^.sectors); + + if not quick then + begin + system.write('clearing device... '); + + for i := 0 to device^.sectors-1 do + device^.write(@sector, i, 1); + + writeln('done'); + end; // if !quick + + + fsBlockShift := 10; {minimum is 1024} + while (1 shl fsBlockShift < fsBlockSize) do inc(fsBlockShift); + + // allocate a new superBlock and clear it + + new(sb); + if (sb = NIL) then exit; + fillchar(sb^, sizeof(diskSuperBlock), 0); + + // device^.sectors is the number of 512 byte sectors + + lBlockShift := fsBlockSize shr 9; + writeln('lBlockShift: ', lBlockShift); + logicalBlocks := device^.sectors div lBlockShift; // logical blocks + writeln('logical blocks: ', logicalBlocks); + + with sb^ do + begin + strcopy(name, 'UbixFS'); + magic1 := UBIXFS_MAGIC_1; + fsByteOrder := 0; + blockSize := fsBlockSize; + blockShift := fsBlockShift; + numBlocks := logicalBlocks; + usedBlocks := 0; + inodeSize := fsBlockSize; + magic2 := UBIXFS_MAGIC_2; + blocksPerAG := min(65536, blockSize shl 3); + AGShift := 10; // realistically it should be a min of 13 + while (1 shl AGShift <> blocksPerAG) do inc(AGShift); + numAGs := (dword(numBlocks)+(blocksPerAG-1)) div blocksPerAG; + + flags := UBIXFS_CLEAN; + setBlockRun(logBlocks, 0, 0, 0); + logStart := 0; + logEnd := 0; + magic3 := UBIXFS_MAGIC_3; + setIAddr(indicies, 0, 0, 0); + setIAddr(rootDir, 0, 0, 0); + end; // with + // create the free block list inode + + getmem(inode, fsBlockSize); // make the inode the same size as a block + if (inode = NIL) then exit; + + fillchar(inode^, sb^.inodeSize, 0); + + with inode^ do + begin + magic := UBIXFS_INODE_MAGIC; + + // inodes point to themselves + setIAddr(inodeNum, 0, sb^.logBlocks.len+1, 1); // the +1 for the start might be wrong + setIAddr(parent, 0, 0, 0); + + // If the partition is larger than 550GB we will need to allocate more than + // one blockRun for the BAT. + fsBATSize := logicalBlocks shr 3; // compute how many bytes the BAT takes up + fsBATBlocks := (fsBATSize + fsBlockSize-1) div fsBlockSize; + writeln('fsBATSize: ', fsBATSize); + writeln('fsBATBlocks: ', fsBATBlocks); + setBlockRun(blocks.direct[0], 0, inodeNum.start+1, fsBATBlocks); + + blocks.size := (fsBATBlocks * fsBlockSize); // round the file size up to the nearest block size + blocks.maxDirect := blocks.size-1; + blocks.blockCount := fsBATBlocks; + + uid := 0; + gid := 0; + mode := S_IFDIR; + + flags := INODE_NO_FLAG; + + setIAddr(attributes, 0, 0, 0); + inodeSize := sb^.inodeSize; + + end; {with} + sb^.BAT := inode^.inodeNum; + writeln('writing out superblock to sector 1'); + device^.write(sb, 1, 1); + // note that the superblock does not contain proper inode addresses for the + // BAT and rootDir. Those are sync'd to disk in the destructor after they've been + // created below. + + system.write('Writing out BAT inode... '); + // write out the Block Allocation Table inode + with inode^.inodeNum, sb^ do + device^.write(inode, + ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT), + inodeSize shr SECTOR_SHIFT + ); // device^.write + writeln('done'); + // Okay, the superblock is the only piece of information needed + // to mount an UbixFS partition + new(fs, init(device)); + assert(fs <> NIL); + assert(fs^.vfs_init() = 0); + + // The freshly formatted parition is now mounted. + // From here we need to create: + // BAT (Block Allocation Table) file contents + // journal + // root dir + + // inode still points to the BAT. Clear out the file in case people decided to + // use quick formatting + for i := 0 to (dword(inode^.blocks.size) div sizeof(sector))-1 do + fs^.writeDataStream(sb^.BAT, i*sizeof(sector), @sector, sizeof(sector)); + setBlockRun(blockRun, 0, 0, 1); + fs^.markusedBlocks(blockRun); // mark superBlock block as used + // mark the journal as used here + fs^.markUsedBlocks(sb^.BAT); // mark the BAT inode as used + // now mark the BAT file blocks as used. Note that if the BAT takes up + // more than one blockRun (unlikely, but not impossible) then this code + // will have to check for that. + writeln('BAT inode blocks.direct[0].len: ', inode^.blocks.direct[0].len); + fs^.markUsedBlocks(inode^.blocks.direct[0]); + + // If the number of blocksPerAG doesn't divide evenly into the + // number of logical blocks, mark out all the "remainder" blocks + // We need to mark the blocks as used in the BAT manually because + // if we do it through markUsedBlocks() it will change the usedBlocks + // count. + + remainderBlocks := (fsBATBlocks shl sb^.blockShift) - fsBATSize; + writeln('RemainderBlocks: ', remainderBlocks); + if (remainderBlocks <> 0) then + begin + // check if the remainder blocks are evenly divisible by 8 + if (remainderBlocks and 7 <> 0) then + begin + blockBit := uInt8(1 shl (remainderBlocks and 7)) -1; + fs^.writeDataStream(sb^.BAT, fsBatSize, @blockBit, 1); + blockBit := -1; + for i := fsBatSize+1 to (fsBATBlocks shl sb^.blockShift)-1 do + fs^.writeDataStream(sb^.BAT, i, @blockBit, 1); + end + else + begin + blockBit := -1; + for i := fsBatSize to (fsBATBlocks shl sb^.blockShift)-1 do + fs^.writeDataStream(sb^.BAT, i, @blockBit, 1); + end; + end; + + fs^.vfs_mkdir('/'); + if (fs^.vfs_fExist('/.') = -1) then + writeln('Warning! /. doesn''t exist') + else + writeln('fExist(''/.'') returned properly'); + if (fs^.vfs_fExist('/nofile.dat') <> -1) then + writeln('Warning! /nofile.dat exists?') + else + writeln('fExist(''/nofile.dat'') returned properly'); + + if (fs^.vfs_fExist('/nodir/nofile.dat') <> -1) then + writeln('Warning! /nodir/nofile.dat exists?') + else + writeln('fExist(''/nodir/nofile.dat'') returned properly'); + +// writeln('fs^.vfs_fExist(''/.'') returned: ', fs^.vfs_fExist('/.')); +// writeln('fs^.vfs_fExist(''/nofile.dat'') returned: ', fs^.vfs_fExist('/nofile.dat')); + fs^.printSuperBlock; + + dispose(fs, done); + writeln('Format complete!'); + dispose(sb); + freemem(inode, fsBlockSize); + +end; // TUbixfS.vfs_format + +function TUbixFS.vfs_init; +var index:uInt32; +begin + // This function should probably not be called more than once without + // calling the appropiate shutdown code + writeln('TUbixFS.vfs_init()'); + result := -1; + if (dev = NIL) then exit; + new(superBlock); + assert(superBlock <> NIL); + if (superBlock = NIL) then exit; + fillchar(superBlock^, sizeof(superBlock^), 0); // better safe than sorry + // read in the superBlock. It's always the 1st block on the partition (counting from 0) + system.write('reading in superBlock... '); + dev^.read(superBlock, 1, 1); + writeln('done'); + + assert(superBlock^.magic1 = UBIXFS_MAGIC_1); + assert(superBlock^.magic2 = UBIXFS_MAGIC_2); + assert(superBlock^.magic3 = UBIXFS_MAGIC_3); + assert(strcomp(superBlock^.name, 'UbixFS') = 0); + assert((1 shl superBlock^.blockShift) = superBlock^.blockSize); + assert((1 shl superBlock^.AGShift) = superBlock^.blocksPerAG); + assert(superBlock^.flags = UBIXFS_CLEAN); + + // create the file decriptor tree + + + // The blockCache tree has a [key::value] pair of [block address::pointer to cacheNode]. + // The blockTime tree has a [key::value] pair of [last used time::pointer to cacheNode]. + + // create the block cache tree + new(blockCache, init('', 256, BT_INT64, NIL)); + // create the block last used time tree + new(blockTime, init('', 256, BT_INT64, NIL)); + + // Create the file descriptors and related stuff + + // vnids is a tree which has a [key::value] pair of [iAddr::pointer to vNode]. + new(vnids, init('', 256, BT_INT64, NIL)); + + // vnodeQueue is a queue which has a list of available FD handles + new(vNodeQueue, init(maxFDEntry, sizeof(uInt32))); + // fill the vNodeQueue with a list of all available FD handles + for index := 0 to maxFDEntry-1 do + vNodeQueue^.enqueue(index); + + // Allocate and clear the file descriptor array + GetMem(fileDescriptors, maxFDEntry*sizeof(TVNode)); + fillchar(fileDescriptors^, maxFDEntry*sizeof(TVNode), 0); + + // The last used time is obtained through the RDTSC instruction to give us a unique + // and increasing timestamp. When the cache is full we drop off the least recently + // used block. Because the tree is sorted, this will be the first entry. + + result := 0; // success +end; // TUbixFS.vfs_init + +function TUbixFS.vfs_mkdir; +const + dot:array[0..1] of char = '.'#0; + dotdot:array[0..2] of char = '..'#0; +var + inode:PUbixFSInode; + iAddr:inodeAddr; + bRun:TBlockRun; + len:integer; + dirTree:PbTree; + u:uPtr; +begin + // XXX ToDo: + // Ability to add sub directories + + result := -1; + assert(path <> NIL); + assert(superBlock <> NIL); + if (path = NIL) or (superBlock = NIL) then exit; + + len := strlen(path); + assert(len <> 0); + if (len = 0) then exit; + + if (len = 1) and (uInt8ArrayPtr(path)^[0] = ord('/')) then + begin + writeln('Creating root directory'); + // wanting to create the root. Check to see if the root dir inode is empty + assert(not isValidIAddr(superBlock^.rootDir)); + if isValidIAddr(superBlock^.rootDir) then exit; // should set a proper error code here + setIAddr(iAddr, 0, 0, 0); // technically the "parent" iaddr + inode := allocInode(iAddr); // allocate an inode (parent points to la-la land) + assert(inode <> NIL); // make sure it isn't nil + if (inode = NIL) then exit; + inode^.mode := S_IFDIR; + superBlock^.rootDir := inode^.inodeNum; // assign the root dir + saveBlock(superBlock^.rootDir, inode); // save the inode + u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr + + // open the root dir + with superBlock^.rootDir do + writeln('Root dir inode: ', AG, ':', start, ':', len); + writeln('Opening root dir tree'); + new(dirTree, init(iAddrToStr(superBlock^.rootDir), 1024, BT_PCHAR, new(PUbixBTreeVFS, init(@self)))); + writeln('Inserting ''.'''); + // Get ready to insert the '.' :: inode^.inodeNum key :: value pair + writeln('dirTree^.Insert(@dot, u) returned ', dirTree^.Insert(@dot, u)); // '.' points to ourself + dispose(dirTree, done); // close and save the directory +// saveInode(inode); // save the inode + //freemem(inode, inode^.inodeSize); // free the memory associate with the inode (saved above) + end + else + begin + // subdir support goes here + end; + + result := 0; +end; // TUbixFS.vfs_mkdir + +function TUbixFS.vfs_read; +var + inode:PUbixFSInode; + u:uPtr; +begin + result := -1; + if (buffer = NIL) or (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + with fileDescriptors^[handle] do + begin + assert(readDataStream(iAddr, position, buffer, size) = 0); + position := position + int64(size); + result := size; + end; // with +end; // TUbixFS.vfs_read + +function TUbixFS.vfs_seek; +begin + result := -1; + if (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + fileDescriptors^[handle].position := newPosition; + result := 0; +end; // TUbixFS.vfs_seek + +function TUbixFS.vfs_write; +begin + result := -1; + if (buffer = NIL) or (fileDescriptors = NIL) or (handle >= maxFDEntry) then exit; + with fileDescriptors^[handle] do + begin + assert(writeDataStream(iAddr, position, buffer, size) = 0); + position := position + int64(size); + result := size; + end; // with +end; // TUbixFS.vfs_write + + +function TUbixFS.writeDataStream; +var + tmpBlock:uInt8ArrayPtr; + blockRun:TBlockRun; + size, remainder:uInt32; + blockStart:uInt32; + blocksToWrite:uInt32; + bytesToWrite:uInt32; + inodeChanged:boolean; + inode:PUbixFSInode; +begin + // XXX ToDo: + // Read/Write through cache + // fix block extension size + // change whether the inode is saved or not + result := -1; + if ((buf = NIL) or (superBlock = NIL)) then exit; + inode := loadBlock(iAddr); + tmpBlock := NIL; + + inodeChanged := FALSE; + if (position + length > getFileSpace(inode)) then + begin + extendFile(inode, position+length); +// inode^.blocks.size := position+length; // update the blocks + writeln('inode^.blocks.size (after extendFile) = ', inode^.blocks.size); + end + else + if (position + length > inode^.blocks.size) then + begin + // The above case checks to see if we're writing out past + // the end of the allocated space on the device. If we do, we extend + // out the file. However, if we're just writing past the end of + // the file, we have to update the size. + + inode^.blocks.size := position+length; + // XXX To Do: + // Might want to clear out the blocks we're skipping past, otherwise + // there might be garbage in there + assert(saveBlock(iAddr, inode)); // save the inode + end; + + size := length; + remainder := uInt32(position mod superBlock^.blockSize); + + if (remainder <> 0) then + begin + // the position we've been asked to write to doesn't align to + // a block size. We need to read in the previous block and pad + // the data + blockStart := uInt32(position shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + + if (tmpBlock = NIL) then exit; + // read in the block + readDataStream(iAddr, blockStart, tmpBlock, superBlock^.blockSize); + bytesToWrite := min(size, (superBlock^.blockSize - remainder)); + move(buf^, tmpBlock^[remainder], bytesToWrite); + saveBlock(blockRun, tmpBlock); + inc(buf, bytesToWrite); + dec(size, bytesToWrite); + position := position + bytesToWrite; + end; // if remainder != 0 + + remainder := size mod superBlock^.blockSize; + dec(size, remainder); + + // Size is currently in bytes. The remainder variable above holds the number + // of remaining bytes (if any) outside full blocks we have to write. + + // convert the size into blocks + size := size shr superBlock^.blockShift; + + while (size > 0) do + begin + blockRun := findBRun(@inode^.blocks, uInt32(position shr superBlock^.blockShift)); + assert(blockRun.len > 0); + blocksToWrite := min(blockRun.len, size); + for blocksToWrite := 0 to min(blockRun.len, size)-1 do + begin + saveBlock(blockRun, buf); + inc(blockRun.start); // This might wrap around? + dec(blockRun.len); // might be unnecessary + dec(size); + inc(buf, superBlock^.blockSize); + end; + end; // while + + if (remainder <> 0) then + begin + // There is less than a block of data left to write. Read in the old block + blockStart := uInt32((position+length-remainder) shr superBlock^.blockShift); + blockRun := findBRun(@inode^.blocks, blockStart); + assert(blockRun.len > 0); + // obtain a pointer to the block + tmpBlock := loadBlock(blockRun); + assert(tmpBlock <> NIL); + if (tmpBlock = NIL) then exit; + move(buf^, tmpBlock^[0], remainder); + saveBlock(blockRun, tmpBlock); + end; + result := 0; +end; // TUbixFS.writeDataStream + +destructor TUbixFS.done; +var + searchRec:BTreeSearchRec; + inode:PUbixfsInode; + cacheNode:PCacheNode; + dirtyCount:integer; + u:uPtr; +begin + sync(); + // XXX ToDo: + // close all "open" files manually. + dispose(vnids, done); + dispose(vNodeQueue, done); + FreeMem(fileDescriptors, maxFDEntry*sizeof(TVNode)); + vnids := NIL; + vNodeQueue := NIL; + fileDescriptors := NIL; + + if (blockTime <> NIL) then dispose(blockTime, done); + if (blockCache <> NIL) then + begin + dirtyCount := 0; + fillchar(u, sizeof(u), 0); + while (blockCache^.getFirstKey(searchRec)) do + begin + cacheNode := searchRec.value.p; + assert(cacheNode <> NIL); + if (cacheNode^.dirty) then inc(dirtyCount); + u.p := cacheNode; + blockCache^.Delete(@cacheNode^.address, u); + dispose(cacheNode, done); + end; + if (dirtyCount <> 0) then writeln('Dirty blocks in cache: ', dirtyCount); + dispose(blockCache, done); + end; + if (superBlock <> NIL) then dispose(superBlock); +end; // TUbixFS.done + +procedure TUbixFS.examine; +var + dirTree:PBTree; + searchRec:BTreeSearchRec; + u:uPtr; +begin + writeln('------------- examining FS -----------------'); + write('Opening root dir...'); + new(dirTree, open(IAddrToStr(superblock^.rootDir), new(PUbixBTreeVFS, init(@self)))); + //dirTree^.PrintList('rootDir.txt'); + writeln('done'); + writeln('Root directory has ', dirTree^.getKeyCount(), ' entrie(s)'); + dirTree^.getFirstKey(searchRec); + repeat + with searchRec.value.iAddr do + write('value: ', AG, ':', start, ':', len); + writeln(' key: ', pchar(searchRec.key)); + until not dirTree^.findNext(searchRec); + dispose(dirTree, done); + if (blockCache <> NIL) then writeln('Block cache has ', blockCache^.getKeyCount(), ' entries'); + writeln('------------------ done --------------------'); +end; + +end. \ No newline at end of file diff --git a/uformat.pas b/uformat.pas new file mode 100755 index 0000000..a85cd7f --- /dev/null +++ b/uformat.pas @@ -0,0 +1,191 @@ +program uFormat; + +uses device, ubixfs, diskDrv, dos, crt, debug; + +procedure usage; +begin + writeln('Format a disk or file for use with UbixOS'); + writeln; + writeln('uFormat drive: [-Q] [-B] [-A:BlockSize]'); + writeln('uFormat file [-Q] [-B] [-A:BlockSize] [-S:Size]'); + writeln; + writeln; + writeln(' -Q Performs a quick format.'); + writeln(' -S Specifies the size in blocks'); + writeln(' -B Allocates space on the formatted disk for system files.'); + writeln(' -A Overrides the default allocation unit size. Default is 1024'); + writeln(' UbixFS supports 1024, 2048, 4096, and 8192 block sizes'); + halt; +end; + +function upcasestr(ins:string):string; +var + s:string; + i:integer; +begin + s[0] := ins[0]; + for i:=1 to length(ins) do + s[i] := upcase(ins[i]); + result := s; +end; + +function getBlockSize:uInt32; +var + s:string; + i:integer; + code:integer; + size:integer; +begin + + result := 1024; {default} + for i:=1 to paramCount do + if (pos('-A:', upcasestr(paramstr(i))) <> 0) then + begin + s := copy(paramstr(i), 4, length(paramstr(i)) - 3); + val(s, size, code); + if (code<>0) then + begin + writeln('Error parsing allocation unit size: ', paramstr(i)); + halt; + end; + result := size; + break; + end; {if} +end; {getBlockSize} + +function getSize:uInt32; +var + s:string; + i:integer; + code:integer; + size:integer; +begin + result := 0; + for i:=1 to paramCount do + if (pos('-S:', upcasestr(paramstr(i))) <> 0) then + begin + s := copy(paramstr(i), 4, length(paramstr(i)) - 3); + val(s, size, code); + if (code<>0) then + begin + writeln('Error parsing size: ', paramstr(i)); + halt; + end; + result := size; + break; + end; {if} +end; {getSize} + +function getQuick:boolean; +var i:integer; +begin + result := FALSE; + for i := 1 to paramcount do + if (upcasestr(paramstr(i)) = '-Q') then + begin + result := TRUE; + exit; + end; {if} +end; {getQuick} + +procedure verifyBlockSize(size:uInt32); +begin + if ((size = 1024) or (size = 2048) or (size = 4096) or (size = 8192)) then exit; + writeln('Allocation unit must be one of the following: 1024, 2048, 4096, 8192'); + halt; +end; {verifyBlockSize} + +var + quick:boolean; + i:integer; + drivefile:string; + dev:PDevice; + f:file; + key:char; + size:uInt32; + fsBlockSize:uInt32; + p:pointer; + ufs:PUbixFS; +begin + + if (paramcount = 0) then usage; + drivefile := paramstr(1); + + fsBlockSize := getBlockSize; + verifyBlockSize(fsBlockSize); + quick := getQuick; + + if ((length(drivefile) = 2) and (drivefile[2] = ':')) then + begin + // physical drive + writeln('Physical drive: ', drivefile); + end + else + begin + // file + size := getSize; + if (fsearch(drivefile, '')<>'') then + begin + writeln('Warning! ', drivefile, ' already exists. Overwrite? [Y/n]'); + repeat key := upcase(readkey) until key in [#27, #13, 'Y', 'N']; + if (key in ['N', #27]) then halt; + assign(f, drivefile); + reset(f, 1); + + if (size = 0) then + begin + size := filesize(f); + if (size mod fsBlockSize <> 0) then + begin + writeln('File size of ', drivefile, ' isn''t evenly divisible by ', fsBlockSize); + writeln('Use the -S: option to override the file block count'); + close(f); + halt; + end; + size := size div fsBlockSize; + end; + + if (size = 0) then + begin + writeln('Block count is 0! Aborting!'); + close(f); + halt; + end; + + if (size <> filesize(f)) then + begin + getmem(p, fsBlockSize); + fillchar(p^, fsBlockSize, 0); + + rewrite(f, fsBlockSize); + for i:=0 to size-1 do + blockwrite(f, p^, 1); + + freemem(p, fsBlockSize); + end; + close(f); + dev := dev_diskDrive(drivefile); + end + else // file didn't exist, create one + begin + if (size = 0) then + begin + writeln('Block count is 0. Use the -S: option to specify the size in blocks'); + halt + end; + assign(f, driveFile); + rewrite(f, fsBlockSize); + getmem(p, fsBlockSize); + fillchar(p^, fsBlockSize, 0); + for i := 0 to size - 1 do + blockwrite(f, p^, 1); + freemem(p, fsBlockSize); + close(f); + dev := dev_diskDrive(driveFile); + end; + + end; {else} + new(ufs, init(NIL)); + ufs^.vfs_format(dev, fsBlockSize, quick); + dispose(ufs, done); +end. \ No newline at end of file diff --git a/viewfs.pas b/viewfs.pas new file mode 100755 index 0000000..72a5843 --- /dev/null +++ b/viewfs.pas @@ -0,0 +1,25 @@ +uses UbixFS, Device, ramDrive, diskDrv, debug, strings, dos, crt, math; + +var + // drive : PRamDrive; + drive:PDiskDrive; + fs: PUbixFS; + +begin + +// new(drive, init(2880)); // allocate a ramdrive that's 2880 sectors long (floppy size) +// new(drive, init(8192*4)); // allocate a ramdrive that's big + new(drive, init('ubixfs.dsk')); + new(fs, init(drive)); + fs^.vfs_init(); + fs^.printSuperBlock(); + fs^.visualize(); + fs^.examine(); + + dispose(fs, done); + dispose(drive, done); + + writeln('sizeof(diskSuperBlock): ',sizeof(TdiskSuperBlock)); + writeln('sizeof(ubixfsInode): ',sizeof(TubixfsInode)); + writeln('sizeof(dataStream): ', sizeof(TdataStream)); +end. \ No newline at end of file