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, '