Newer
Older
ubixfs-2 / ubixfs.backup.pas
@flameshadow flameshadow on 10 Jun 2005 54 KB UbixFS
unit ubixfs;

interface

uses device, lists;

const INODE_NO_FLAG     = $00000000;
const INODE_IN_USE      = $00000001;

const ATTR_INODE        = $00000004;
const INODE_LOGGED      = $00000008;
const INODE_DELETED     = $00000010;
const PERMANENT_FLAGS   = $0000ffff;
const INODE_NO_CACHE    = $00010000;
const INODE_WAS_WRITTEN = $00020000;
const S_IFDIR           = $00040000;  // POSIX
const NO_TRANSACTION    = $00040000;

const NUM_DIRECT_BLOCKS = 16;

const UBIXFS_MAGIC_1    = $A0A0A0A;
const UBIXFS_MAGIC_2    = $B0B0B0B;
const UBIXFS_MAGIC_3    = $C0C0C0C;
const UBIXFS_INODE_MAGIC = $3BBE0AD9;

const UBIXFS_CLEAN = $434C454E;
const UBIXFS_DIRTY = $44495254;

const SECTOR_SHIFT = 9;
const SECTOR_SIZE = 1 shl SECTOR_SHIFT;

type
  int8          = shortInt;
  int16         = smallInt;
  int32         = longInt;

  uInt8         = byte;
  uInt16        = word;
  uInt32        = dWord;
{ Common Integer array definitions }

type
  int8ArrayPtr  = ^int8Array;
  int8Array     = array[ 0..0 ] of int8;

  int16ArrayPtr = ^int16Array;
  int16Array    = array[ 0..0 ] of int16;

  int32ArrayPtr = ^int32Array;
  int32Array    = array[ 0..0 ] of int32;

  { Common Unsigned Integer array definitions }

type
  uInt8ArrayPtr = ^uInt8Array;
  uInt8Array    = array[ 0..0 ] of uInt8;

  uInt16ArrayPtr= ^uInt16Array;
  uInt16Array   = array[ 0..0 ] of uInt16;

  uInt32ArrayPtr= ^uInt32Array;
  uInt32Array   = array[ 0..0 ] of uInt32;

type
(*  TBlockRun = packed record
    AG     : int32;
    start  : uInt16;
    len    : uInt16;
  end;

  inodeAddr = TblockRun;

  uPtr = packed record
   case integer of
     0: (i      : int32);
     1: (u      : uInt32);
     2: (s      : single);
     3: (d      : double);
     4: (p      : pointer);
     5: (offset : int64);
     6: (iAddr  : inodeAddr);
  end;
*)
  PdiskSuperBlock = ^diskSuperBlock;
  diskSuperBlock = packed record
    name        : array[0..31] of char;
    magic1      : int32;

    fsByteOrder : int32;

    // blockSize on disk 
    blockSize   : uInt32;

    // number of bits needed to shift a block number to get a byte address
    blockShift  : uInt32;

    numBlocks   : int64;
    usedBlocks  : int64;

    inodeSize   : uInt32;
    magic2      : uInt32;

    blocksPerAG : uInt32;
    AGShift     : uInt32;
    numAGs      : uInt32;

    flags       : uInt32;

    logBlocks   : TBlockRun;
    logStart    : int64;
    logEnd      : int64;

    magic3      : int32;
    BAT         : inodeAddr;
    rootDir     : inodeAddr;
    indicies    : inodeAddr;

    pad         : array[0..371] of byte;
  end; // diskSuperBlock

  PDataStream = ^TDataStream;
  TDataStream = packed record
    direct        : array[0..NUM_DIRECT_BLOCKS-1] of TBlockRun;
    maxDirect     : uInt32;
    maxDirectU    : uInt32; // upper 32 bits
    indirect      : TBlockRun;
    maxIndirect   : uInt32;
    maxIndirectU  : uInt32; // upper 32 bits 
    doubleIndirect: TBlockrun;
    maxDIndirect  : uInt32;
    maxDIndirectU : uInt32; // upper 32 bits
    size          : uInt32;
    sizeU	  : uInt32; // upper 32 bits
  end; // TDataStream

  PubixfsInode = ^ubixfsInode;
  ubixfsInode = packed record
    magic         : int32;
    inodeNum      : inodeAddr;
    uid           : uInt32;
    gid           : uInt32;
    mode          : int32;
    flags         : int32;
    createTime    : int64;
    modifiedTime  : int64;
    parent        : inodeAddr;
    attributes    : inodeAddr;
    iType         : uInt32;
    inodeSize     : uInt32;
    data          : uPtr;         {this was the etc field in bfs}
    refCount      : uInt32;
    blocks        : TDataStream;
    blockCount    : uInt32;
    smallData     : array[0..0] of byte;
  end; // ubixfsInode

  PfileDescriptor = ^TfileDescriptor;
  TfileDescriptor = packed record
    magic  : uInt32;
    inode  : pointer;
    offset : int64;
    size   : int64;
    
  end; // fileDescriptor

  vfs_abstract = object
   private
    prev : ^vfs_abstract;
    next : ^vfs_abstract;
    dev  : PDevice;
   public
    constructor init;
    destructor done; virtual;
  end; // vfs_abstract

  PUbixFS = ^TUbixFS;
  TUbixFS = object(vfs_abstract)
   private
    superBlock    : ^diskSuperBlock;
    freeBlockList : PfileDescriptor;
    InodeCache    : PbTree;
    procedure       assert(x:boolean);
    function        allocBRun(blockRun:TBlockRun):TBlockRun;
    function        allocInode(parentIAddr:inodeAddr):PUbixfsInode;
    function        extendFile(inode:PubixfsInode; numBlocks:uInt32):integer;
    function        findBRun(dataStream:PDataStream; blockNum:uInt32):TBlockRun;
    function        findBRunDirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun;
    function        findBRunIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun;
    function        findBRunDIndirect(dataStream:PDataStream; blockNum:uInt32):TBlockRun;
    function        freeUsedBlocks(blockRun:TBlockRun):integer;
    function        getFileSize(inode:PUbixfsInode):uInt32; // limits to 4GB
    function        getFileSpace(inode:PUbixfsInode):uInt32; // limits to 4GB
    function        iAddrToStr(iAddr:inodeAddr):string;
    function        isValidIAddr(iAddr:inodeAddr):boolean;
    function        loadInode(iaddr:inodeAddr):PUbixfsInode;
    function        markUsedBlocks(blockRun:TBlockRun):integer;
    function        nextAG(AG:uInt32):uInt32;
    function        pathToIAddr(path:PChar):inodeAddr;

    function        readDataStream(inode:PubixfsInode; position:uInt32; buf:pointer; length:uInt32):integer; // position limits files to 4GB
    function        saveInode(inode:PUbixfsInode):integer;
    procedure       setBlockRun(var blockRun:TBlockRun; AG:uInt32; start, len:uInt16);
    procedure       setIAddr(var inode:inodeAddr; AG:uInt32; start, len:uInt16);
    function        strToIAddr(s:string):inodeAddr;
    function        unloadInode(inode:PubixfsInode):integer;
    function        writeDataStream(inode:PubixfsInode; position:uInt32; buf:pointer; length:uInt32):integer;  // position limits files to 4GB
   public
    constructor     init(device:Pdevice);
    procedure       printSuperBlock;
    function        vfs_fCreate(filename:PChar):int32;
    function        vfs_fExist(path:PChar):integer;
    function        vfs_format(device:PDevice; fsBlockSize:uInt32; quick:boolean):integer;
    function        vfs_init:integer;
    function        vfs_mkdir(path:PChar):integer;
    function        vfs_read(filedes:int64; position:uInt32; buffer:pointer; size:uInt32):uInt32;    // limits to 4GB
    function        vfs_write(filedes:int64; position:uInt32; buffer:pointer; size:uInt32):uInt32;  // limits to 4GB
    procedure       examine;
    destructor      done; virtual;
  end; // TUbixFS
 
implementation

uses strings, dos, math, lists;

type 
  PUbixBTreeVFS = ^TUbixBTreeVFS;
  TUbixBTreeVFS = object(TBTreeVFS)
   private
    fs          : PUbixFS;
    inode       : PUbixFSInode;
    position    : uInt32;  // limits files to 4GB
    inodeSize   : uInt32;
   public
    constructor init(_fs:PUbixFS);
    function    fClose:boolean; virtual;
    function    fCreate(const filename:string):boolean; virtual;
    function    fExist(const filename:string):boolean; virtual;
    function    fOpen(const filename:string):boolean; virtual;
    function    fRead(var buf; size:uInt32):boolean; virtual;   
    function    fSeek(offset:uInt32):boolean; virtual;
    function    fWrite(var buf; size:uInt32):boolean; virtual;
    destructor  done; virtual; 
  end; // TUbixBTreeVFS

constructor TUbixBTreeVFS.init;
begin
//  writeln('TUbixBTreeVFS.init()');
  fs         := _fs;  
  inode      := NIL;
  inodeSize  := 0;
  position   := 0;
end; // TUbixBTreeVFS.init;

function TUbixBTreeVFS.fClose;
begin
  if (fs = NIL) or (inode = NIL) then exit;
  fs^.unloadInode(inode);   
  inode      := NIL;
  inodeSize  := 0;
  position   := 0;
end; // TUbixBTreeVFS.fClose

function TUbixBTreeVFS.fCreate;
begin
  // I *think* that this code is only called for the
  // B^Tree. Therefore, the file technically exists, but just
  // has nothing in it when we call fCreate.
  result := fOpen(filename);  
end; // TUbixBTreeVFS.fCreate

function TUbixBTreeVFS.fExist;
var 
  tmpIAddr:inodeAddr;
  tmpInode:PUbixfsInode;
begin
  result := FALSE;
  assert(fs <> NIL);
  if (fs = NIL) then exit;
  assert(fs^.superBlock <> NIL);
  if (fs^.superBlock = NIL) then exit;
  tmpIAddr := fs^.strToIAddr(filename);
  if not fs^.isValidIAddr(tmpIAddr) then exit;

  // If we've created an inode, but haven't written any data to it, then
  // the B^Tree hasn't written out a superblock, so technically the file doesn't
  // exist, and this is a really long run-on sentence.
  tmpInode := fs^.loadInode(tmpIAddr);
  result := (tmpInode <> NIL) and (tmpInode^.blocks.size <> 0);
  fs^.unloadInode(tmpInode);
end; // TUbixBTreeVFS.fExist

function TUbixBTreeVFS.fOpen;
var 
  iAddr : inodeAddr;
begin
  result := FALSE;
  if (fs = NIL) then exit;
  if (fs^.superBlock = NIL) then exit;
  iAddr := fs^.strToIAddr(filename);
  position := 0;
  inode := fs^.loadInode(iAddr);
  result := (inode <> NIL);
end;  // TUbixBTreeVFS.fOpen

function TUbixBTreeVFS.fRead;
begin
  result := FALSE;
  if (fs = NIL) or (inode = NIL) then exit;
//  with inode^.inodeNum do
//    write('fRead(', AG, ':', start, ':', len, ', ');
//  write(position, ', ');
//  write('@buf, ');

  result := (fs^.readDataStream(inode, position, @buf, size) = 0);
  inc(position, size);
end; // TUbixBTreeVFS.fRead

function TUbixBTreeVFS.fSeek;
begin
  // uhm.. if the desired offset is beyond the end of the file, 
  // should we not extend out the file? Or does that occur in writeDataStream ...
  position := offset;
  result := TRUE;
end; // TUbixBTreeVFS.fSeek

function TUbixBTreeVFS.fWrite;
begin
  result := FALSE;
  if (fs = NIL) or (inode = NIL) then exit;
//  with inode^.inodeNum do
//    write('fWrite(', AG, ':', start, ':', len, ', ');
//  write(position, ', ');
//  write('@buf, ');
  result := (fs^.writeDataStream(inode, position, @buf, size) = 0);
  inc(position, size);
end; // TUbixBTreeVFS.fWrite;

destructor TUbixBTreeVFS.done;
begin
  if ((inode <> NIL) and (inodeSize <> 0)) then FreeMem(inode, inodeSize);
  inodeSize  := 0;
  inode      := NIL;
  fs         := NIL;  // don't destruct it since we didn't allocate it
end; // TUbixBTreeVFS.done

constructor vfs_abstract.init;
begin
  next := NIL;
  prev := NIL;
  dev  := NIL;
end; // vfs_abstract.init

destructor vfs_abstract.done;
begin
end; // vfs_abstract.done

constructor TUbixFS.init;
begin
  inherited init;
  freeBlockList := NIL;
  inodeCache := NIL;
  superBlock := NIL;
  dev := device;
end; // TUbixFS.init

procedure TUbixFS.assert(x:boolean);
begin
  if not x then runerror(1000);
end; // TUbixFS.assert

function TUbixFS.allocBRun;
var
  inode:PUbixfsInode;
  FBL:int8ArrayPtr;
  FBLSize:uInt32;
  bitOffset:uInt32;
  byteOffset:uInt32;
  curBlock, baseBlock:uInt32;
  freeCount:uInt32;
begin
  setBlockRun(result, 0, 0, 0);
  if (superBlock = NIL) then exit;
  if (blockRun.AG > superBlock^.numAGs) then exit;
  if (superBlock^.usedBlocks + blockRun.len > superBlock^.numBlocks) then exit; // should probably give a louder warning
  inode := loadInode(superBlock^.BAT);
  assert(inode <> NIL);  // should probably check for failure
  // now that the BAT inode is loaded, we can scan through it looking for
  // a free bit

  // we read in one block at a time
  FBLSize := superBlock^.blockSize;
  GetMem(FBL, FBLSize);
  assert(FBL <> NIL);
  fillchar(FBL^, FBLSize, 0);  // better safe than sorry

  assert(readDataStream(inode, blockRun.AG shl (superblock^.AGShift shr 3), FBL, FBLSize) = 0); 
  byteOffset := blockRun.start shr 3;
  bitOffset := 7-(blockRun.start and 7);
  baseBlock := (blockRun.AG shl superBlock^.AGShift) + blockRun.start;
  curBlock := baseBlock;
  freeCount := 0;
  // an optimization would be to check to see if the byte is -1, thus skipping 8 at a time
  // This will need to be implemented (but can be later)
  repeat

    inc(curBlock);

    if (FBL^[byteOffset] and (1 shl bitOffset) = 0) then 
      inc(freeCount) 
    else 
      freeCount := 0;

    if (freeCount = blockRun.len) then break;

    if (bitOffset = 0) then 
      begin
        bitOffset := 7;
        inc(byteOffset);
      end
    else dec(bitOffset);

    if (byteOffset = superBlock^.blocksPerAG) then
      begin
        byteOffset := 0;
        curBlock := 0;
        inc(blockRun.AG);
        if (blockRun.AG = superBlock^.numAGs) then blockRun.AG := 0;
        assert(readDataStream(inode, blockRun.AG shl (superblock^.AGShift shr 3), FBL, FBLSize) = 0); 
        bitOffset := 7;
      end;

  until (curBlock = baseBlock);
  
  if (freeCount = blockRun.len) then   // we found a run
    begin
      blockRun.AG := (curBlock-blockRun.len) shr superBlock^.AGShift;
      blockRun.start := (curBlock-blockRun.len) and (superBlock^.blocksPerAG-1);
      markUsedBlocks(blockRun);
      result := blockRun;
    end
  else writeln('Failure in allocBRun()');
  FreeMem(FBL, FBLSize); 
  assert(unloadInode(inode) = 0);
  
end; // TUbixFS.allocBRun

function TUbixFS.allocInode;
var
  inode:PUbixfsInode;
  inodeSize:uInt32;
  blockRun:TBlockRun;
  
begin
  // XXX ToDo:
  // need to fill in the rest of the fields in the inode record

  result := NIL;
  if (superBlock = NIL) then exit;
  assert(parentIAddr.AG < superBlock^.numAGs);
  if (parentIAddr.AG >= superBlock^.numAGs) then exit;  // parent addr is outside out block count?
  // ask for a single block somewhere around the beginning of the requested AG
  setBlockRun(blockRun, parentIAddr.AG, 0, 1); 
  blockRun := allocBRun(blockRun);
  assert(blockRun.len <> 0);
  if (blockRun.len = 0) then exit;     // this would be bad
  inodeSize := superBlock^.inodeSize;
  GetMem(inode, inodeSize);
  assert(inode <> NIL);
  if (inode = NIL) then exit;
  fillchar(inode^, inodeSize, 0);

  with inode^ do
    begin
      magic := UBIXFS_INODE_MAGIC;

      // inodes point to themselves
      inodeNum := blockRun;

      uid := 0;  // this doesn't mean much here
      gid := 0;  
      // mode := ??
      flags := INODE_NO_FLAG;
      // createTime := 
      // modifiedTime :=

      parent := parentIAddr;
      setIAddr(attributes, 0, 0, 0);
      inodeSize := superBlock^.inodeSize;
      // data := 0;
      refCount := 0;  // maybe 1?
      // there's no data yet in the file, so don't 
      // set blocks to anything (cleared above)
      blockCount := 0;

    end; // with
  result := inode;
end; // TUbixFS.allocInode

function TUbixFS.extendFile;
var
  blockRun, exBlockRun:TBlockRun;
  i:integer;
  found:boolean;
begin
  // XXX ToDo:
  // Need to add support for indirect and double-indirect blocks
  // Fix 4GB issue
  // Check to see if the block extension is the last one in direct blocks

  result := -1;  // failure by default
  // basic sanity checks
  if (inode = NIL) or (superBlock = NIL) then exit;

  // if the file size is 0 then this is the first extension.
  if (inode^.blocks.size = 0) then  // first extension
    begin
      // a file's data is stored in the next allocation group from it's parent
      // inode. This will help distribute the data across the disk.
      setBlockRun(exBlockRun, nextAG(inode^.inodeNum.AG), 0, numBlocks);
      exBlockRun := allocBRun(exBlockRun);    // try to allocate a blockrun
      assert(exBlockRun.len <> 0);
      if (exBlockRun.len = 0) then exit;      // erp. Couldn't extend the file
      inode^.blocks.direct[0] := exBlockRun;  // Set the first direct run
      // The space allocated for the file can and may be different than the
      // actual size.  Set the maxDirect to the size (in bytes) of how much space
      // we allocated
      inode^.blocks.maxDirect := numBlocks shl superBlock^.blockShift;  
    end // size = 0
  else
    begin
    // the size of the file wasn't 0 (handled above). We need to extend the 
    // file out.  Find where the last blocks of the file are

    // inode^.blocks.size is now a 32-bit integer because the compiler doesn't
    // properly support 64-bit ints.  Therefore, we are limited to 4GB
    blockRun := findBRun(@inode^.blocks, inode^.blocks.size shr superBlock^.blockShift);
    assert(blockRun.len <> 0);     // hm.. was exBlockRun
    if (blockRun.len = 0) then exit;           // This would be odd
    exBlockRun     := blockRun;                // copy the last run to exBlockRun
    exBlockRun.len := numBlocks;               // set the requested number of blocks
    exBlockRun     := allocBRun(exBlockRun);   // allocate a block run near the previous one
    if (exBlockRun.len = 0) then exit;         // should probably yell louder about this

    // blockRun points to the last blockRun structure associated with this inode
    if (exBlockRun.AG = blockRun.AG) and (exBlockRun.start = blockRun.start+1) then
      begin
        // the extended block run continues a previous run.
        // scan through to find what the last run was

        with inode^, blocks do
          begin
            // XXX only works for direct block runs for now
            // I'm going to have to put a special check in to see if 
            // we're the last entry in the direct field
            if (direct[high(direct)].len <> 0) then assert(false);

            for i := low(direct) to high(direct) do
              if (direct[i].len = 0) then break;
            assert(i <> low(direct));  // big problemo
            // make sure we don't overflow the length
            if (dword(direct[i-1].len) + numBlocks < 65535) then
              begin
                inc(direct[i-1].len, numBlocks);  // update the previous entry
              end
            else  // oops, we overflowed the length
              begin
                // need to check to see if we were the last direct entry. If we were
                // then we need to allocate an indirect block and store it in there
                if (i = high(direct)) then assert(false);
                direct[i] := exBlockRun;
              end; // else
          end;  // with
      end
    else    // extended blockRun isn't contiguous to the previous one
      begin
        
        with inode^, blocks do
          begin
            // XXX only works for direct block runs for now
 
            // I'm going to have to put a special check in to see if 
            // we're the last entry in the direct field
            if (direct[high(direct)].len <> 0) then assert(false);

            for i := low(direct) to high(direct) do
              if (direct[i].len = 0) then break;
            // i points to the entry in the direct[] block runs to the first free slot
            direct[i] := exBlockRun;    // set this entry to the extended block run
            // now update the maxDirect size
            maxDirect := maxDirect + numBlocks shl superBlock^.blockShift;
          end; // with
      end;  // else
  end;  // else size <> 0

  inc(inode^.blockCount, numBlocks);  // update the blockCount in the inode
  // no matter where we stored the extended blockRun, we have updated the inode

  assert(saveInode(inode) = 0);  // 0 result means success
  result := 0;  // no error
end; // TUbixFS.extendFile

function TUbixFS.findBRun;
var
  position:uInt32;  // limits filesize to 4gb
begin
  setBlockRun(result, 0, 0, 0);
  if (dataStream = NIL) then exit;
  position := blockNum shl superBlock^.blockShift;
  if (position < dataStream^.maxDirect) then
    result := findBRunDirect(dataStream, blockNum)
  else if (position < dataStream^.maxIndirect) then
    result := findBRunIndirect(dataStream, blockNum)
  else if (position < dataStream^.maxDIndirect) then
    result := findBRunDIndirect(dataStream, blockNum)
  else writeln('Block position is not mapped by dataStream');
end; // TUbixFS.findBRun

function TUbixFS.findBRunDirect;
var
  maxBlock:uInt32;
  sum, i, offset:uInt32;
begin
  // set the result to a nil blockrun
  setBlockRun(result, 0, 0, 0);

  // maxDirect is in bytes, so shift it down to blocks
  maxBlock := dword(dataStream^.maxDirect) shr superblock^.blockShift;

  if (blockNum > maxBlock) then
    begin
      writeln('Requested block outside direct block run range');
      exit;
    end;

  sum := 0;
  with dataStream^ do
    for i := low(direct) to high(direct) do
      with direct[i] do
        begin
          if (blockNum < sum + len) then                // found it
            begin
              offset := blockNum - sum;
              setBlockRun(result, AG, start + offset, len - offset);
              exit
            end;
          inc(sum, len);
        end;  // for
end; // TUbixFS.findBRunDirect

function TUbixFS.findBRunIndirect;
begin
  // XXX ToDo:
  // load and scan the indirect block for the corresponding block run
  setBlockRun(result, 0, 0, 0);
end; // TUbixFS.findBRunIndirect

function TUbixFS.findBRunDIndirect;
begin
  // XXX ToDo:
  // find the position in the double-indirect range
  // load the appropiate double-indirect block and get the blockRun

  setBlockRun(result, 0, 0, 0);
end; // TUbixFS.findBRunDIndirect

function TUbixFS.freeUsedBlocks;
var 
  inode:PUbixfsInode;
  FBL:int8ArrayPtr;
  FBLSize:uInt32;
  startByte, endByte:uInt32;
  bitCount, bitOffset:uInt32;
  startBlock:uInt32;
  byteOffset:uInt32;
begin

  result := -1;
  // basic sanity checks
  if ((superBlock = NIL) or (blockRun.len = 0)) then exit;

  // first thing to do is load the BAT inode
  inode := loadInode(superBlock^.BAT);
  assert(inode <> NIL);  

  // Okay.. inode is the BAT inode. We need this to read from/write to the BAT

  // Compute how much of the BAT we have to read in.
  with blockRun, superBlock^ do
    begin
      startBlock := (AG shl AGShift) + start;
      startByte := startBlock shr 3;       // divide by 8 since it's a bitfield
      endByte := (startBlock + len) shr 3; // divide by 8 since it's a bitfield
      FBLSize := endByte - startByte + 1;

      GetMem(FBL, FBLSize);
      assert(readDataStream(inode, startByte, FBL, FBLSize) = 0); 
      bitOffset := 7-(startBlock and 7);
      byteOffset := 0;

      for bitCount := 1 to len do
        begin   
          FBL^[byteOffset] := FBL^[byteOffset] and not (1 shl bitOffset);
          if (bitOffset = 0) then 
            begin
              bitOffset := 7;
              inc(byteOffset);
            end
          else dec(bitOffset);
        end; // for bitCount
      // save back out the modified block
      assert(writeDataStream(inode, startByte, FBL, FBLSize) = 0);
 
      FreeMem(FBL, FBLSize);
      assert(unloadInode(inode) = 0);
      usedBlocks := usedBlocks - len;
    end; // with

  result := 0;
end; // TUbixFS.freeUsedBlocks

function TUbixFS.getFileSize;
begin
  // XXX ToDo:
  // Fix to support sizes > 4GB
  result := 0;
  if (inode = NIL) then exit;
  // inode^.blocks.size is now a 32-bit integer. 
  // This limits the filesize to 4GB
  result := inode^.blocks.size;
end;  // TUbixFS.getFileSize

function TUbixFS.getFileSpace;
begin
  // getFileSpace() will return the size of the file's allocated space
  // XXX ToDo:
  // Fix to support sizes > 4GB
  result := 0;
  if (inode = NIL) or (superBlock = NIL) then exit;
  result := inode^.blockCount shl superBlock^.blockShift;
end; // TUbixFS.getFileSpace

function TUbixFS.iAddrToStr;
var s:string;
begin
  // Note: this won't work in C/C++ because strings are really null
  // terminated character arrays. So, the B^Tree won't hold a string
  // with nulls in it properly
  length(s) := sizeof(iAddr);
  move(iAddr, s[1], sizeof(iAddr));
  result := s;
end; // TUbixFS.iAddrToStr;

function TUbixFS.loadInode;
var 
  u:uPtr;
  inode:PUbixfsInode;
begin
  // XXX ToDo:
  // Read through file cache
  // Do more sanity checks on the inode

  result := NIL;
  if (superBlock = NIL) or (dev = NIL) or (inodeCache = NIL) then exit;

  if inodeCache^.Find(@iAddr, u) then  // inode was in cache
    begin
      inode := u.p;
      assert(inode <> NIL);
    end
  else
    begin
      // allocate space for the inode
      GetMem(inode, superBlock^.inodeSize);
      assert(inode <> NIL);
      // clear it (this isn't absolutely necessary, but better
      // safe than sorry). Also, it's only done the first time  
      // the inode is loaded, so speed isn't as much a concern
      fillchar(inode^, superBlock^.inodeSize, 0);
      with iAddr do
        writeln('inode: ', AG, ':', start, ':', len);
      with iAddr, superBlock^ do
        dev^.read(dev,
                  inode,
                  ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT),
                  inodeSize shr SECTOR_SHIFT
                 ); // dev^.read
      // should probably check more than this
      if (inode^.magic <> UBIXFS_INODE_MAGIC) then 
        begin
          FreeMem(inode, superBlock^.inodeSize);
          writeln('TUbixFS.loadInode() failed to load an inode!');
          exit;
        end;
      // now insert the pointer into the InodeCache
      u.p := inode;
      assert(inodeCache^.Insert(@iAddr, u));
    end;
  inc(inode^.refCount);  // increment the reference count
  result := inode;
end; // TUbixFS.loadInode

function TUbixFS.markUsedBlocks;
var 
  inode:PUbixfsInode;
  FBL:uInt8ArrayPtr;
  FBLSize:uInt32;
  startByte, endByte:uInt32;
  bitCount, bitOffset:uInt32;
  startBlock:uInt32;
  byteOffset:uInt32;
begin

  result := -1;
  // basic sanity checks
  if ((superBlock = NIL) or (blockRun.len = 0)) then exit;
with blockRun do
writeln('---- marking used blocks: ', AG, ':', start, ':', len);

  // first thing to do is load the BAT inode
  inode := loadInode(superBlock^.BAT);
  assert(inode <> NIL);

  // Okay.. inode is the BAT inode. We need this to read from/write to the BAT

  // Compute how much of the BAT we have to read in.
  with blockRun, superBlock^ do
    begin
      startBlock := (AG shl AGShift) + start;
      startByte := startBlock shr 3;
      endByte := (startBlock + len) shr 3;
      FBLSize := endByte - startByte + 1;
      GetMem(FBL, FBLSize);
      assert(readDataStream(inode, startByte, FBL, FBLSize) = 0); 
      fillchar(FBL^, FBLSize, 0);  // better safe than sorry
      bitOffset := 7-(startBlock and 7);
      byteOffset := 0;
      for bitCount := 1 to len do
        begin   
          FBL^[byteOffset] := FBL^[byteOffset] or (1 shl bitOffset);
          if (bitOffset = 0) then 
            begin
              bitOffset := 7;
              inc(byteOffset);
            end
          else dec(bitOffset);
        end; // for bitCount
      assert(writeDataStream(inode, startByte, FBL, FBLSize) = 0);
 
      FreeMem(FBL, FBLSize);
      assert(unloadInode(inode) = 0);

      usedBlocks := usedBlocks + len;
    end; // with
  result := 0;
end; // TUbixFS.markUsedBlocks

function TUbixFS.nextAG;
begin
  // there's no error we can throw from here if superblock is NIL,
  // so don't bother to check.  Just hope whomever calls this
  // function has done some basic sanity checks
  result := (AG+1) mod superBlock^.numAGs
end; // TUbixFS.nextAG

function TUbixFS.pathToIAddr;

  function recursePath(path:PChar; len:integer; iAddr:inodeAddr):inodeAddr;
    var 
      dirTree:PBTree;
      u:uPtr;
      nameSize:integer;
    begin
      // if the string length is 0 then we have found the inode in the previous
      // recurse, so return what we found.
      if (len = 0) then 
        begin
          result := iAddr;
          exit;     
        end
      else
        setIAddr(result, 0, 0, 0);
      // hm. If we're looking for something like ``/.'' then the ``/'' is stripped
      // off in the main call leaving only ``.'', which means that this malformed 
      // path check would trigger.  Same with /foo/bar if bar is a dir.
      //if uInt8ArrayPtr(path)^[0] = ord('/') then exit;  // malformed path
      if not (isValidIAddr(iAddr)) then exit;           // sanity check
      nameSize := 0;
      while (len > 0) and (uInt8ArrayPtr(path)^[nameSize] <> ord('/')) do
        begin
          inc(nameSize);
          dec(len);    
        end;
      uInt8ArrayPtr(path)^[nameSize+1] := 0;
      if (len <> 0) then dec(len);
     
      new(dirTree, open(IAddrToStr(iAddr), new(PUbixBTreeVFS, init(@self))));
      if (dirTree = NIL) then exit;  // hm.. what happens to the PUbixBTreeVFS is this is NIL?
      // note that the dirTree is a tree of BT_PCHAR, and searches are case sensitive.
      // if the tree doesn't exist, Find() should return false.
      if dirTree^.Find(path, u) then result := recursePath(path + nameSize + 1, len, u.iAddr);
      dispose(dirTree, done);
    end; // recursePath

var len:integer;
begin
  setIAddr(result, 0, 0, 0);

  if (path = NIL) or (superBlock = NIL) then exit;  

  len := strlen(path);
  if (len = 0) then exit;                            // empty string
  if uInt8ArrayPtr(path)^[0] <> ord('/') then exit;  // malformed path

  result := recursePath(path+1, len-1, superBlock^.rootDir);

end; // TUbixFS.pathToIAddr

procedure TUbixFS.printSuperBlock;
begin
  if (superBlock = NIL) then exit;
  with superBlock^ do
    begin
      writeln('superBlock^.name........... ', name);
      writeln('superBlock^.magic1......... ', hex(magic1));
      writeln('superBlock^.fsByteOrder.... ', fsByteOrder);
      writeln('superBlock^.blockSize...... ', blockSize);
      writeln('superBlock^.blockShift..... ', blockShift);
      writeln('superBlock^.numBlocks...... ', numBlocks);
      writeln('superBlock^.usedBlocks..... ', usedBlocks);
      writeln('superBlock^.magic2......... ', hex(magic2));
      writeln('superBlock^.blocksPerAG.... ', blocksPerAG);
      writeln('superBlock^.AGShift........ ', AGShift);
      writeln('superBlock^.numAGs......... ', numAGs);
      writeln('superBlock^.flags.......... ', hex(flags));
      writeln('superBlock^.magic3......... ', hex(magic3));
      with superBlock^.BAT do
        writeln('superBlock^.BAT............ ', AG, ':', start, ':', len);
      with superBlock^.rootDir do
        writeln('superBlock^.rootDir........ ', AG, ':', start, ':', len);
    end;
end; // TUbixFS.printSuperBlock

function TUbixFS.saveInode;
begin
  // XXX ToDo:
  // Write through cache
  result := -1;
  if (superBlock = NIL) or (dev = NIL) or (inode = NIL) then exit;
  
  with inode^.inodeNum, superBlock^ do
    dev^.write(dev,
               inode,
               ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT),
               inodeSize shr SECTOR_SHIFT
              ); // device^.write
  result := 0;
end; // TUbixFS.saveInode

function TUbixFS.isValidIAddr;
begin
  result := (iAddr.len <> 0) and (iAddr.AG < superBlock^.numAGs); 
end; // TUbixFS.isValidIAddr

function TUbixFS.readDataStream;
var 
  tmpBlock:uInt8ArrayPtr;
  blockRun:TBlockRun;
  size, remainder:uInt32;
  blockStart:uInt32;
  blocksToRead:uInt32;
  bytesToRead:uInt32;
begin
  // XXX ToDo:
  // Read from cache

  result := -1;
  if ((inode = NIL) or (buf = NIL)) then exit;
  tmpBlock := NIL;
  size := length;
  remainder := position mod superBlock^.blockSize;

  if (remainder <> 0) then
    begin
      // the position we've been asked to read from doesn't align to
      // a block size. We need to read in the previous block and pad
      // the data
      blockStart := position shr superBlock^.blockShift;
      blockRun := findBRun(@inode^.blocks, blockStart);
      assert(blockRun.len > 0);
      GetMem(tmpBlock, superBlock^.blockSize);
      assert(tmpBlock <> NIL);
      if (tmpBlock = NIL) then exit;

      with superBlock^, blockRun do
        dev^.read(dev,
                  tmpBlock,
                  ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
                  blockSize shr SECTOR_SHIFT
                 ); // dev^.write        

      bytesToRead := min(size, (superBlock^.blockSize - remainder));
      move(tmpBlock^[remainder], buf^, bytesToRead);
      inc(buf, bytesToRead);
      dec(size, bytesToRead);
      inc(position, bytesToRead);
    end; // if remainder != 0

  remainder := size mod superBlock^.blockSize;
  dec(size, remainder);

  // Size is currently in bytes. The remainder variable above holds the number
  // of remaining bytes (if any) outside full blocks we have to write.

  // convert the size into blocks
  size := size shr superBlock^.blockShift;

  while (size > 0) do
    begin
      blockRun := findBRun(@inode^.blocks, position shr superBlock^.blockShift);
      assert(blockRun.len > 0);
      blocksToRead := min(blockRun.len, size);
      with inode^, superBlock^, blockRun do
        dev^.read(dev,
                  buf,
                  ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
                  blockSize shr SECTOR_SHIFT
                 ); // dev^.read
      dec(size, blocksToRead);
      inc(buf, blocksToRead shl superBlock^.blockShift);
    end; // while 

  if (remainder <> 0) then
    begin
      // There is less than a block of data left to read.
      blockStart := (position+length-remainder) shr superBlock^.blockShift;
      blockRun := findBRun(@inode^.blocks, blockStart);
      assert(blockRun.len > 0);
      if (tmpBlock = NIL) then GetMem(tmpBlock, superBlock^.blockSize);
      assert(tmpBlock <> NIL);
      with inode^, superBlock^, blockRun do
        dev^.read(dev,
                  tmpBlock,
                  ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
                  blockSize shr SECTOR_SHIFT
                 ); // dev^.read
      move(tmpBlock^, buf^, remainder);      
    end;
  if (tmpBlock <> NIL) then FreeMem(tmpBlock, superBlock^.blockSize);
  result := 0;
end; // TUbixFS.readDataStream

procedure TUbixFS.setBlockRun;
begin
  blockRun.AG    := AG;
  blockRun.start := start;
  blockRun.len   := len;
end; // TUbixFS.setBlockRun

procedure TUbixFS.setIAddr;
begin
  inode.AG    := AG;
  inode.start := start;
  inode.len   := len;
end; // TUbixFS.setIAddr

function TUbixFS.strToIAddr;
begin
  // Note: this won't work in C/C++ because strings are really null
  // terminated character arrays. So, the B^Tree won't hold a string
  // with nulls in it properly
  // begin fiendishly clever hack
  move(s[1], result, sizeof(inodeAddr));
  // end fiendishly clever hack
end; // TUbixFS.strToIAddr

function TUbixFS.unloadInode;
var
  u:uPtr;
begin
  // XXX ToDo:
  // I guess this would be a good place to determine if the
  // file has been deleted, and free all resources associated
  // with it after the ref count drops to 0
  result := -1;
  if (inode = NIL) or (inodeCache = NIL) then exit;

  dec(inode^.refCount);  // decrese the reference count
  if (inode^.refCount = 0) then
    begin
      // the last handle to the inode was closed, so de-allocate
      // the memory for the inode and delete it out of the cache
      u.p := inode;
      inodeCache^.Delete(@inode^.inodeNum, u);
      // we have inodes in the cache, but they might have been modified
      // The proper solution to this is to not have an inode cache and 
      // instead use a real block cache and write out modified blocks.
      // For the time being, save out this inode to the device
      saveInode(inode);
  {    if (dev <> NIL) then 
        with inode^.inodeNum, superBlock^ do
          dev^.write(dev, 
                     inode, 
                     ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT),
                     inodeSize shr SECTOR_SHIFT
                     ); // device^.write }
      FreeMem(inode, inode^.inodeSize);
    end;
  result := 0;
end; // TUbixFS.unloadInode

function TUbixFS.vfs_fCreate;
var 
  inode:PUbixFSInode;
  dirTree:PBTree;
  dirIAddr, fileIAddr:inodeAddr;
  filedes:int64;
  u:uPtr;
  searchRec:BTreeSearchRec;
  len : integer;
  path:PChar;
  filenamePtr:PChar; 
  curPos:integer;
begin
  result := -1;  
  if (filename = NIL) or (superBlock = NIL) then exit;
  // we need to split the filename into it's component path and filename pieces.
  // From there we need to see if the file exists.  If it does then I guess we 
  // should truncate the file and load the inode.  Otherwise allocate a new inode
  len := strlen(filename);
  if (len = 0) then exit;
  if uInt8ArrayPtr(filename)^[0] <> ord('/') then exit;  // malformed path
  GetMem(path, len+1);
  strCopy(path, filename);
  curPos := len;

  setIAddr(dirIAddr, 0, 0, 0);  // zero out the inode address for the directory
  setIAddr(fileIAddr, 0, 0, 0); // zero out the inode address for the file

  while (curPos > 0) and (uInt8ArrayPtr(path)^[curPos] <> ord('/')) do dec(curPos);
  fileNamePtr := path + curPos + 1;
  if (curPos = 0) then
    begin
      // file is in the root dir
      writeln('path: /');
      writeln('filename: ', fileNamePtr);
      dirIAddr := pathToIAddr('/');
    end
  else
    begin
      uInt8ArrayPtr(path)^[curPos] := 0; // null out the separator
      writeln('Path: ', path);
      writeln('Filename: ', fileNamePtr);
      dirIAddr := pathToIAddr(path);
    end; 

  with dirIAddr do
    writeln('iaddr to path: ', AG, ':', start, ':', len);
  assert(isValidIAddr(dirIAddr));
  if not isValidIAddr(dirIAddr) then exit;
  // should probably check to make sure that we're opening a directory and not a file
  write('creating new dir tree...');
  new(dirTree, open(IAddrToStr(dirIAddr), new(PUbixBTreeVFS, init(@self))));
  writeln('done');
  if (dirTree = NIL) then exit;
  if dirTree^.Find(fileNamePtr, u) then 
    begin
      writeln(fileNamePtr, ' already exists');
    end
  else
    begin
      writeln('creating new file: ', fileNamePtr);
      writeln('allocating new inode');
      inode := allocInode(dirIAddr);   // alloc a new inode (pass in the parent iaddr)
      assert(inode <> NIL);            // make sure it isn't nil
      if (inode = NIL) then exit;
      saveInode(inode);                // save the new inode
      u.iAddr := inode^.inodeNum;      // copy the inode addr to a uPtr      
      // now add the file to the directory
      writeln('Inserting ', fileNamePtr, ' into directory returned ', dirTree^.Insert(fileNamePtr, u));
    end;  
  dispose(dirTree, done);
  FreeMem(path, len+1);
  result := 0;
end; // TUbixFS.vfs_fCreate

function TUbixFS.vfs_fExist;
begin
  // XXX ToDo:
  // Find out why fExist('/.') fails
  result := -1;
  if ((superBlock = NIL) or (path = NIL)) then exit;
  if (not isValidIAddr(superBlock^.rootDir)) then exit;
  if isValidIAddr(pathToIAddr(path)) then result := 0;
end; // TUbixFS.vfs_fExist

function TUbixFS.vfs_format;
var
  sector:array[0..511] of byte;
  fs:PUbixFS;
  logicalBlocks, fsBATSize, fsBATBlocks:uInt32;
  sb:PdiskSuperBlock;
  inode:PubixfsInode;
  i, fsBlockShift, lBlockShift, nodeSize, keyLength:integer;
  blockRun:TBlockRun;
  remainderBlocks:uInt32;
  blockBit:shortint;
begin
  result := -1;
  if (device = NIL) then exit;
  if (fsBlockSize < 1024) then
    begin
      writeln('Blocksize must be >= 1024');
      exit;
    end;
  // zero out the sector
  fillchar(sector, sizeof(sector), 0);

  writeln('device^.sectors: ', device^.sectors);

  if not quick then
    begin
      system.write('clearing device... ');

      for i := 0 to device^.sectors-1 do
        device^.write(device, @sector, i, 1);

      writeln('done');
    end; // if !quick


  fsBlockShift := 10;  {minimum is 1024}
  while (1 shl fsBlockShift < fsBlockSize) do inc(fsBlockShift);

  // allocate a new superBlock and clear it

  new(sb);
  if (sb = NIL) then exit;
  fillchar(sb^, sizeof(diskSuperBlock), 0);

  // device^.sectors is the number of 512 byte sectors

  lBlockShift := fsBlockSize shr 9;
  writeln('lBlockShift: ', lBlockShift);
  logicalBlocks := device^.sectors div lBlockShift;        // logical blocks
  writeln('logical blocks: ', logicalBlocks);

  with sb^ do
    begin
      strcopy(name, 'UbixFS');
      magic1      := UBIXFS_MAGIC_1;
      fsByteOrder := 0;
      blockSize   := fsBlockSize;
      blockShift  := fsBlockShift;
      numBlocks   := logicalBlocks;
      usedBlocks  := 0;            
      inodeSize   := fsBlockSize;
      magic2      := UBIXFS_MAGIC_2;
      blocksPerAG := min(65536, blockSize shl 3);
      AGShift     := 10;  // realistically it should be a min of 13
      while (1 shl AGShift <> blocksPerAG) do inc(AGShift);
      numAGs      := (dword(numBlocks)+(blocksPerAG-1)) div blocksPerAG;

      flags       := UBIXFS_CLEAN;
      setBlockRun(logBlocks, 0, 0, 0);
      logStart    := 0;
      logEnd      := 0;
      magic3      := UBIXFS_MAGIC_3;
      setIAddr(indicies, 0, 0, 0);
      setIAddr(rootDir, 0, 0, 0);
    end; // with
  // create the free block list inode 

  getmem(inode, fsBlockSize);  // make the inode the same size as a block
  if (inode = NIL) then exit;

  fillchar(inode^, sb^.inodeSize, 0);

  with inode^ do
    begin
      magic := UBIXFS_INODE_MAGIC;

      // inodes point to themselves
      setIAddr(inodeNum, 0, sb^.logBlocks.len+1, 1); // the +1 for the start might be wrong
      setIAddr(parent, 0, 0, 0);

      // If the partition is larger than 550GB we will need to allocate more than
      // one blockRun for the BAT.
      fsBATSize := logicalBlocks shr 3;  // compute how many bytes the BAT takes up
      fsBATBlocks := (fsBATSize + fsBlockSize-1) div fsBlockSize;
      writeln('fsBATSize: ', fsBATSize);
      writeln('fsBATBlocks: ', fsBATBlocks);
      blockCount := fsBATBlocks;
      setBlockRun(blocks.direct[0], 0, inodeNum.start+1, fsBATBlocks);

      blocks.size := (fsBATBlocks * fsBlockSize); // round the file size up to the nearest block size
      blocks.maxDirect := blocks.size-1;
      uid := 0;
      gid := 0;
      mode := S_IFDIR;

      flags := INODE_NO_FLAG;

      setIAddr(attributes, 0, 0, 0);
      inodeSize := sb^.inodeSize;

  end; {with}
  sb^.BAT := inode^.inodeNum;
  writeln('writing out superblock to sector 1');
  device^.write(device, sb, 1, 1);
  // note that the superblock does not contain proper inode addresses for the
  // BAT and rootDir. Those are sync'd to disk in the destructor after they've been
  // created below. 
    
  system.write('Writing out BAT inode... ');
  // write out the Block Allocation Table inode
  with inode^.inodeNum, sb^ do
    device^.write(device,
                  inode,
                  ((AG shl AGShift) + start) * (inodeSize shr SECTOR_SHIFT),
                  inodeSize shr SECTOR_SHIFT
                 ); // device^.write 
  writeln('done');
  // Okay, the superblock is the only piece of information needed
  // to mount an UbixFS partition
  new(fs, init(device));
  assert(fs <> NIL);
  assert(fs^.vfs_init() = 0);

  // The freshly formatted parition is now mounted. 
  // From here we need to create:
  //   free block list file contents
  //   journal
  //   root dir 

  // inode still points to the BAT. Clear out the file in case people decided to
  // use quick formatting
  for i := 0 to (dword(inode^.blocks.size) div sizeof(sector))-1 do
    fs^.writeDataStream(inode, i*sizeof(sector), @sector, sizeof(sector));
  setBlockRun(blockRun, 0, 0, 1);
  fs^.markusedBlocks(blockRun);         // mark superBlock block as used
  // mark the journal as used here
  fs^.markUsedBlocks(inode^.inodeNum);  // mark the BAT inode as used
  // now mark the BAT file blocks as used. Note that if the BAT takes up
  // more than one blockRun (unlikely, but not impossible) then this code
  // will have to check for that.
  writeln('BAT inode blocks.direct[0].len: ', inode^.blocks.direct[0].len);
  fs^.markUsedBlocks(inode^.blocks.direct[0]);  

  // If the number of blocksPerAG doesn't divide evenly into the
  // number of logical blocks, mark out all the "remainder" blocks
  // We need to mark the blocks as used in the FBL manually because
  // if we do it through markUsedBlocks() it will change the usedBlocks
  // count.

  remainderBlocks := (fsBATBlocks shl sb^.blockShift) - fsBATSize;
  writeln('RemainderBlocks: ', remainderBlocks);
  if (remainderBlocks <> 0) then
    begin
      // check if the remainder blocks are evenly divisible by 8
      if (remainderBlocks and 7 <> 0) then
        begin
          blockBit := uInt8(1 shl (remainderBlocks and 7)) -1;
          fs^.writeDataStream(inode, fsBatSize, @blockBit, 1);
          blockBit := -1;
          for i := fsBatSize+1 to (fsBATBlocks shl sb^.blockShift)-1 do
            fs^.writeDataStream(inode, i, @blockBit, 1);
        end
      else
        begin
          blockBit := -1;
          for i := fsBatSize to (fsBATBlocks shl sb^.blockShift)-1 do
            fs^.writeDataStream(inode, i, @blockBit, 1);
        end;
    end;

  fs^.vfs_mkdir('/');
  //writeln(fs^.vfs_fExist('/.'));  // <- this blows up!?
  
  fs^.printSuperBlock;
  
  dispose(fs, done);
  writeln('Format complete!');
  dispose(sb);
  freemem(inode, fsBlockSize);
   
end; // TUbixfS.vfs_format

function TUbixFS.vfs_init;
begin
  // This function should probably not be called more than once without
  // calling the appropiate shutdown code
  writeln('TUbixFS.vfs_init()');
  result := -1;
  if (dev = NIL) then exit;
  new(superBlock);
  assert(superBlock <> NIL);
  if (superBlock = NIL) then exit;
  fillchar(superBlock^, sizeof(superBlock^), 0); // better safe than sorry
  // read in the superBlock. It's always the 1st block on the partition (counting from 0)
  system.write('reading in superBlock... ');
  dev^.read(dev, superBlock, 1, 1);
  writeln('done');

  assert(superBlock^.magic1 = UBIXFS_MAGIC_1);
  assert(superBlock^.magic2 = UBIXFS_MAGIC_2);
  assert(superBlock^.magic3 = UBIXFS_MAGIC_3);
  assert(strcomp(superBlock^.name, 'UbixFS') = 0);
  assert((1 shl superBlock^.blockShift) = superBlock^.blockSize);
  assert((1 shl superBlock^.AGShift) = superBlock^.blocksPerAG);
  assert(superBlock^.flags = UBIXFS_CLEAN);
  result := 0;  // success
  
  // create the file decriptor tree
  new(inodeCache, init('', 256, BT_INT64, NIL)); 
end; // TUbixFS.vfs_init

function TUbixFS.vfs_mkdir;
const 
  dot:array[0..1] of char = '.'#0;
  dotdot:array[0..2] of char = '..'#0;
var 
  inode:PUbixFSInode;
  iAddr:inodeAddr;
  bRun:TBlockRun;
  len:integer;
  dirTree:PbTree;
  u:uPtr;
begin
  // XXX ToDo:
  // Ability to add sub directories

  result := -1;
  assert(path <> NIL);
  assert(superBlock <> NIL);
  if (path = NIL) or (superBlock = NIL) then exit;

  len := strlen(path);
  assert(len <> 0);
  if (len = 0) then exit;

  if (len = 1) and (uInt8ArrayPtr(path)^[0] = ord('/')) then
    begin
      writeln('Creating root directory');
      // wanting to create the root. Check to see if the root dir inode is empty
      assert(not isValidIAddr(superBlock^.rootDir));
      if isValidIAddr(superBlock^.rootDir) then exit;  // should set a proper error code here
      setIAddr(iAddr, 0, 0, 0);   // technically the "parent" iaddr
      inode := allocInode(iAddr); // allocate an inode (parent points to la-la land)
      assert(inode <> NIL);       // make sure it isn't nil
      if (inode = NIL) then exit;
      superBlock^.rootDir := inode^.inodeNum;  // assign the root dir
      saveInode(inode);           // save the inode
      u.iAddr := inode^.inodeNum; // copy the inode addr to a uPtr

      // open the root dir
      with superBlock^.rootDir do
        writeln('Root dir inode: ', AG, ':', start, ':', len);
      writeln('Opening root dir tree');
      new(dirTree, init(iAddrToStr(superBlock^.rootDir), 1024, BT_PCHAR, new(PUbixBTreeVFS, init(@self))));
      writeln('Inserting ''.''');
      // Get ready to insert the '.' :: inode^.inodeNum key :: value pair
      writeln('dirTree^.Insert(@dot, u) returned ', dirTree^.Insert(@dot, u));     // '.' points to ourself
      dispose(dirTree, done);       // close and save the directory
//      saveInode(inode);           // save the inode      
      freemem(inode, inode^.inodeSize);  // free the memory associate with the inode (saved above)
    end
  else
    begin
      // subdir support goes here
    end;
    
  result := 0;
end; // TUbixFS.vfs_mkdir

function TUbixFS.vfs_read;
var 
  inode:PUbixFSInode;
  u:uPtr;
begin
  result := -1;
  if (buffer = NIL) or (superBlock = NIL) or (inodeCache = NIL) then exit;
  if not inodeCache^.Find(@filedes, u) then exit;
  inode := u.p;
  if (inode = NIL) then exit;
  assert(readDataStream(inode, position, buffer, size) = 0);
  result := size;
end; // TUbixFS.vfs_read

function TUbixFS.vfs_write;
var 
  inode:PUbixFSInode;
  u:uPtr;
begin
  result := -1;
  if (buffer = NIL) or (inodeCache = NIL) then exit;
  if not inodeCache^.Find(@filedes, u) then exit;
  inode := u.p;
  if (inode = NIL) then exit;
  assert(writeDataStream(inode, position, buffer, size) = 0);
  result := size;
end; // TUbixFS.vfs_write


function TUbixFS.writeDataStream;
var 
  tmpBlock:uInt8ArrayPtr;
  blockRun:TBlockRun;
  size, remainder:uInt32;
  blockStart:uInt32;
  blocksToWrite:uInt32;
  bytesToWrite:uInt32;
  inodeChanged:boolean;
begin
  // XXX ToDo:
  // Read/Write through cache
  // fix block extension size
  result := -1;
  if ((inode = NIL) or (buf = NIL) or (superBlock = NIL)) then exit; 
  tmpBlock := NIL;

  inodeChanged := FALSE;
  if (position + length > getFileSpace(inode)) then 
    begin
      // the next line is slightly wrong. We extend 4 blocks,
      // but we may be writing out more than 4 blocks worth of
      // data.
      extendFile(inode, 4);  // XXX pick a reasonable amount to extend by
      inode^.blocks.size := position+length;  // update the blocks
      writeln('inode^.blocks.size = ', inode^.blocks.size); 
      inodeChanged := TRUE;
    end
  else
    if (position + length > inode^.blocks.size) then
      begin
      	// The above case checks to see if we're writing out past
      	// the end of the allocated space on the device. If we do, we extend 
      	// out the file. However, if we're just writing past the end of 
      	// the file, we have to update the size.
      	inodeChanged := TRUE;
      	inode^.blocks.size := position+length;
      end;

  size := length;
  remainder := position mod superBlock^.blockSize;

  if (remainder <> 0) then
    begin
      // the position we've been asked to write to doesn't align to
      // a block size. We need to read in the previous block and pad
      // the data
      blockStart := position shr superBlock^.blockShift;
      blockRun := findBRun(@inode^.blocks, blockStart);
      assert(blockRun.len > 0);
      GetMem(tmpBlock, superBlock^.blockSize);
      assert(tmpBlock <> NIL);
      if (tmpBlock = NIL) then exit;
      // read in the block
      readDataStream(inode, blockStart, tmpBlock, superBlock^.blockSize);
      bytesToWrite := min(size, (superBlock^.blockSize - remainder));
      move(buf^, tmpBlock^[remainder], bytesToWrite);
      with inode^, superBlock^, blockRun do
        dev^.write(dev,
                   tmpBlock,
                   ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
                   blockSize shr SECTOR_SHIFT
                  ); // dev^.write 
      inc(buf, bytesToWrite);
      dec(size, bytesToWrite);
      inc(position, bytesToWrite);
    end; // if remainder != 0

  remainder := size mod superBlock^.blockSize;
  dec(size, remainder);

  // Size is currently in bytes. The remainder variable above holds the number
  // of remaining bytes (if any) outside full blocks we have to write.

  // convert the size into blocks
  size := size shr superBlock^.blockShift;

  while (size > 0) do
    begin
      blockRun := findBRun(@inode^.blocks, position shr superBlock^.blockShift);
      assert(blockRun.len > 0);
      blocksToWrite := min(blockRun.len, size);
      with inode^, superBlock^, blockRun do
        dev^.write(dev,
                   buf,
                   ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
                   blockSize shr SECTOR_SHIFT
                  ); // dev^.write 
      dec(size, blocksToWrite);
      inc(buf, blocksToWrite shl superBlock^.blockShift);
    end; // while

  if (remainder <> 0) then
    begin
      // There is less than a block of data left to write. Read in the old block
      blockStart := (position+length-remainder) shr superBlock^.blockShift;
      blockRun := findBRun(@inode^.blocks, blockStart);
      assert(blockRun.len > 0);
      if (tmpBlock = NIL) then GetMem(tmpBlock, superBlock^.blockSize);
      assert(tmpBlock <> NIL);
      if (tmpBlock = NIL) then exit;
      // read in the block
      readDataStream(inode, blockStart, tmpBlock, superBlock^.blockSize);
      move(buf^, tmpBlock^[0], remainder);
      with inode^, superBlock^, blockRun do
        dev^.write(dev,
                   tmpBlock,
                   ((AG shl AGShift) + start) * (blockSize shr SECTOR_SHIFT),
                   blockSize shr SECTOR_SHIFT
                  ); // dev^.write 
    end;
  if (tmpBlock <> NIL) then FreeMem(tmpBlock, superBlock^.blockSize);
  if (inodeChanged) then saveInode(inode);
  result := 0;
end; // TUbixFS.writeDataStream

destructor TUbixFS.done;
var 
  searchRec:BTreeSearchRec;
  inode:PUbixfsInode;
begin
  inherited done;
  if (InodeCache <> NIL) then
    begin
      if (InodeCache^.getFirstKey(searchRec)) then
        repeat
          inode := searchRec.value.p;
          if (inode <> NIL) then unloadInode(inode);
          //if (inode <> NIL) then freemem(inode, inode^.inodeSize);
        until not InodeCache^.findNext(searchRec);
      writeln('inodes in InodeCache: ', InodeCache^.GetKeyCount());  
      dispose(InodeCache, done);
      InodeCache := NIL;
    end;
  // we need to sync the superblock back out to disk
  // Doing this should probably be done in sync() instead of here.
  // We should also probably honour the clean/dirty flags and only
  // write it out when needed.
  if (dev <> NIL) then dev^.write(dev, superBlock, 1, 1);
  if (superBlock <> NIL) then dispose(superBlock);
end; // TUbixFS.done

procedure TUbixFS.examine;
var 
  dirTree:PBTree;
  searchRec:BTreeSearchRec;
  u:uPtr;
begin
  writeln('------------- examining FS -----------------');
  write('Opening root dir...');
  new(dirTree, open(IAddrToStr(superblock^.rootDir), new(PUbixBTreeVFS, init(@self))));
  //dirTree^.PrintList('rootDir.txt');
  writeln('done');
  writeln('Root directory has ', dirTree^.getKeyCount(), ' entrie(s)');
  write('Getting first key...');    
  dirTree^.getFirstKey(searchRec);
  writeln('done');
  writeln('key: ', pchar(searchRec.key)^);
  with searchRec.value.iAddr do
    writeln('value: ', AG, ':', start, ':', len);
  dispose(dirTree, done);
  writeln('------------------ done --------------------');
end;

end.