unit Blocks;

interface

uses LinuxFS, Windows, ex2, SysUtils;


type

  TBlock = class
  public
    Part     : TLinuxPartition;
    BlockNo  : ULONG;
    GroupNo  : ULONG;
    Block    : Pointer;
    RefCount : ULONG;
    Dirty    : Boolean;

    constructor Create(Partition : TLinuxPartition; BlockNumber : ULong; ReadBlock : Boolean);
    destructor  Destroy; override;
    function    GetIndirectBlockNo(BlockNo : ULONG) : ULONG;
    procedure   SetIndirectBlockNo(BlockNo : ULONG; NewBlock : ULONG);
    procedure   ZeroBlock;

  end;

  //////////////////////////////////////
  //////////////////////////////////////
  //////////////////////////////////////

  TBlockGroup = class
  public
    GroupNo       : ULONG;
    RefCount      : Integer;
    Part          : TLinuxPartition;

    BlockBitmap   : Pointer; // alloc the memory
    INodeBitmap   : Pointer; // and calc these
    INodeTable    : Pointer; // addresses

    Dirty         : Boolean; // Do we need to save changes
    StartBlock    : ULong;

    Info          : Pext2_group;

    constructor Create(Partition : TLinuxPartition; GroupN : ULONG);
    destructor Destroy; override;
    procedure Sync;

    procedure IncLockCount;
    function  DecLockCount : Integer;

    function GetINode(INode : ULONG) : Pext2_inode;

    function AllocNewINode : ULong;
    function AllocNewBlock : ULong;

    function  GetINodeBit(INode : ULONG) : Boolean;
    procedure SetINodeBit(INode : ULONG; Allocate : Boolean);

    function  GetBlockBit(BlockNo : ULONG) : Boolean;
    procedure SetBlockBit(BlockNo : ULONG; Allocate : Boolean);

  end;


implementation

uses ex2explore;

type
  BlockList = record
    BlockNo : ULONG;
  end;
  PBlockList = ^BlockList;


///////////////////////////////////
// TBlock
///////////////////////////////////

constructor TBlock.Create(Partition : TLinuxPartition; BlockNumber : ULong; ReadBlock : Boolean);
begin
   Part := Partition;
   BlockNo  := 0;
   Block    := nil;
   RefCount := 0;
   Dirty    := False;

   BLockNo := BLockNumber;

   if (BlockNo > Part.GetSuper.s_blocks_count) then
   begin
      raise Exception.Create('Block number ' + IntToStr(BlockNo) + ' is out of range!');
   end;

   if BlockNo = 0 then
   begin
      // alloc a block of memory and 0 it.
      GetMem(Block, Part.GetBlockSize);
      ZeroBlock;
   end
   else
   begin
      GroupNo := ((BlockNo - 1) div Part.GetSuper.s_blocks_per_group) + 1;
      Debug('*Lock data block ' + IntToStr(BlockNo) + ' (group = ' + IntToStr(GroupNo) + ')', DebugHigh);

      if ReadBlock then
      begin
         Block := Part.GetBlock(BlockNo, 1);
      end
      else
      begin
         Block := Part.GetBlockMem(1);
      end;
   end;
end;

destructor TBlock.Destroy;
begin
   if RefCount > 0 then
   begin
      Debug('Error : freeing locked block', DebugOff);
   end;
   if Assigned(Block) then
   begin
      if BlockNo = 0 then
      begin
         FreeMem(Block, Part.GetBlockSize);
      end
      else
      begin
         if Dirty then
         begin
            Part.SaveBlock(Block, BlockNo, 1);
         end;
         Part.FreeBlock(Block, 1);
      end;
   end;
end;

procedure TBlock.ZeroBlock;
begin
   ZeroMemory(Block, Part.GetBlockSize);
   Dirty := True;
end;

function TBlock.GetIndirectBlockNo(BlockNo : ULONG) : ULONG;
var
   Offset   : ULONG;
   P        : Pointer;
begin
   if (BlockNo < 1) or (BlockNo > Part.IndirectBlockCount) then
   begin
      raise Exception.Create('Indirect Block ' + IntToStr(BlockNo) + ' is out of range!');
   end;
   Offset := sizeof(ULONG) * (BlockNo - 1);
   P := @PChar(Block)[Offset];
   Result := PBlockList(P).BlockNo;
end;

procedure TBlock.SetIndirectBlockNo(BlockNo : ULONG; NewBlock : ULONG);
var
   Offset   : ULONG;
   P        : Pointer;
begin
   if (BlockNo < 1) or (BlockNo > Part.IndirectBlockCount) then
   begin
      raise Exception.Create('Indirect Block ' + IntToStr(BlockNo) + ' is out of range!');
   end;
   if NewBlock > Part.GetSuper.s_blocks_count then
   begin
      raise Exception.Create('Block number ' + IntToStr(NewBlock) + ' is out of range!');
   end;
   Offset := sizeof(ULONG) * (BlockNo - 1);
   P := @PChar(Block)[Offset];
   PBlockList(P).BlockNo := NewBlock;
   Dirty := True;
end;

///////////////////////////////////
// TBlockGroup
///////////////////////////////////

constructor TBlockGroup.Create(Partition : TLinuxPartition; GroupN : ULONG);
var
   Offset   : ULONG;
begin
   Part        := Partition;
   GroupNo     := GroupN;
   RefCount    := 0;
   Dirty       := False;

   // read in the data
   Info       := Part.GetGroupInfo(GroupNo);
   StartBlock := Info.bg_block_bitmap;

   BlockBitmap := Part.GetBlock(StartBlock, Part.GetBlockHeadSize);

   Offset      := (Info.bg_inode_bitmap - Info.bg_block_bitmap) * Part.GetBlockSize; // this works for me...
   INodeBitmap := @PChar(BlockBitmap)[Offset];

   Offset     := (Info.bg_inode_table - Info.bg_block_bitmap) * Part.GetBlockSize;
   INodeTable := @PChar(BlockBitmap)[Offset];
end;

destructor TBlockGroup.Destroy;
begin
   if RefCount > 0 then
   begin
      Debug('Error : freeing locked group', DebugOff);
   end;
   if Dirty then
   begin
      // We need to save ourself
      Part.SaveBlock(BlockBitmap, StartBlock, Part.GetBlockHeadSize);
   end;
   Part.FreeBlock(BlockBitmap, Part.GetBlockHeadSize);
end;

procedure TBlockGroup.Sync;
begin
   if Dirty then
   begin
      // We need to save ourself
      Part.SaveBlock(BlockBitmap, StartBlock, Part.GetBlockHeadSize);
      Dirty := False;
   end;
end;

procedure TBlockGroup.IncLockCount;
begin
   Inc(RefCount);
end;

function  TBlockGroup.DecLockCount : Integer;
begin
   Dec(RefCount);
   Result := RefCount;
end;

function TBlockGroup.GetINode(INode : ULONG) : Pext2_inode;
var
   Offset : ULONG;
begin
   Offset := sizeof(ext2_inode) * INode;
   Result := Pext2_inode(@PChar(INodeTable)[Offset]);
end;

function TBlockGroup.AllocNewINode : ULong;
var
   Bytes : ULong;
   i     : Integer;
   Data  : Char;
   Start : ULong;
   Found : Boolean;
begin
   Result := 0;
   Found  := False;
   // scan the INode bitmap for a free INode
   Bytes := Part.GetSuper.s_blocks_per_group div 8;
   if GroupNo = 1 then
   begin
      Start := 2; // there are reserved INodes at the start.....
   end
   else
   begin
      Start := 0;
   end;
   for i := Start to Bytes - 1 do
   begin
      Data := PChar(INodeBitmap)[i];
      if Ord(Data) <> $FF then
      begin
         // there is a bit in here that is not set
         Result := i * 8;
         while (ord(data) and 1) <> 0 do
         begin
            // shift right
            Data := Chr(ord(Data) shr 1);
            //and inc count
            inc(Result);
         end;
         Found := True;
         break;
      end;
   end;
   // Make sure we have no exceeded the INode count
   if Result >= part.GetSuper.s_inodes_per_group then
   begin
        Found := False;
   end;
   if not Found then
   begin
      raise Exception.Create('No more free INodes in group ' + IntToStr(GroupNo));
   end;

   // alloc the INode
   if GetINodeBit(Result) then
   begin
      Debug('INode is already allocated!!!!!!', DebugOff);
   end;
   SetINodeBit(Result, True);
   if not GetINodeBit(Result) then
   begin
      Debug('INode is not allocated!!!!!!', DebugOff);
   end;
end;

function TBlockGroup.GetINodeBit(INode : ULONG) : Boolean;
var
   Byte : ULong;
   Bit  : Integer;
   Data : Char;
begin
   Byte := INode div 8;
   Bit  := Inode mod 8;
   Data := PChar(INodeBitmap)[Byte];

   if ((ord(Data) shr Bit) and 1) = 1 then
   begin
      Debug('INode is allocated', DebugHigh);
      Result := True;
   end
   else
   begin
      Debug('INode is free', DebugHigh);
      Result := False;
   end;
end;

procedure TBlockGroup.SetINodeBit(INode : ULONG; Allocate : Boolean);
var
   Byte : ULong;
   Bit  : Integer;
   Data : Char;
begin
   Byte := INode div 8;
   Bit  := Inode mod 8;
   Data := PChar(INodeBitmap)[Byte];

   if GetINodeBit(INode) <> Allocate then // we are changing the bit
   begin
      // Set the bit
      Data := Chr(Ord(Data) or (1 shl Bit));

      if not Allocate then
      begin
         // Now clear it....
         Data := Chr(Ord(Data) xor (1 shl Bit));
      end;

      // Write back the byte
      PChar(INodeBitmap)[Byte] := Data;

      // adjust the count
      if Allocate then
      begin
         //dec the free the count
         dec(Info.bg_free_inodes_count);
         dec(Part.GetSuper.s_free_inodes_count);
      end
      else
      begin
         //inc the count
         inc(Info.bg_free_inodes_count);
         inc(Part.GetSuper.s_free_inodes_count);
      end;
      Part.SetDescDirty;
      Part.SetSuperDirty;
      Dirty := True;
   end;
end;

function TBlockGroup.AllocNewBlock : ULong;
var
   Bytes : ULong;
   i     : Integer;
   Data  : Char;
   Found : Boolean;
begin
   Found := False;
   Result := 0;
   // scan the Block Bitmap for a free block
   Bytes := Part.GetSuper.s_blocks_per_group div 8;
   for i := 0 to Bytes - 1 do
   begin
      Data := PChar(BlockBitmap)[i];
      if Ord(Data) <> $FF then
      begin
         // there is a bit in here that is not set
         Result := i * 8;
         while (ord(data) and 1) <> 0 do
         begin
            // shift right
            Data := Chr(ord(Data) shr 1);
            //and inc count
            inc(Result);
         end;
         Found := True;
         break;
      end;
   end;
   // Make sure we have no exceeded the INode count
   if Result >= part.GetSuper.s_blocks_per_group then
   begin
        Found := False;
   end;
   if not Found then
   begin
      raise Exception.Create('No more free Blocks in group ' + IntToStr(GroupNo));
   end;

   // alloc the Block
   if GetBlockBit(Result) then
   begin
      Debug('Block is already allocated!!!!!!', DebugOff);
   end;
   SetBlockBit(Result, True);
   if not GetBlockBit(Result) then
   begin
      Debug('Block is not allocated!!!!!!', DebugOff);
   end;
end;

function TBlockGroup.GetBlockBit(BlockNo : ULONG) : Boolean;
var
   Byte : ULong;
   Bit  : Integer;
   Data : Char;
begin
   Byte := BlockNo div 8;
   Bit  := BlockNo mod 8;
   Data := PChar(BlockBitmap)[Byte];

   if ((ord(Data) shr Bit) and 1) = 1 then
   begin
      Debug('Block is allocated', DebugHigh);
      Result := True;
   end
   else
   begin
      Debug('Block is free', DebugHigh);
      Result := False;
   end;
end;

procedure TBlockGroup.SetBlockBit(BlockNo : ULONG; Allocate : Boolean);
var
   Byte : ULong;
   Bit  : Integer;
   Data : Char;
   Info : Pext2_group;
begin
   Byte := BlockNo div 8;
   Bit  := BlockNo mod 8;
   Data := PChar(BlockBitmap)[Byte];

   if GetBlockBit(BlockNo) <> Allocate then // we are changing the bit
   begin
      // Set the bit
      Data := Chr(Ord(Data) or (1 shl Bit));

      if not Allocate then
      begin
         // Now clear it....
         Data := Chr(Ord(Data) xor (1 shl Bit));
      end;

      // Write back the byte
      PChar(BlockBitmap)[Byte] := Data;

      // adjust the count
      Info := Part.GetGroupInfo(GroupNo);
      if Allocate then
      begin
         //dec the free the count
         dec(Info.bg_free_blocks_count);
         dec(Part.GetSuper.s_free_blocks_count);
      end
      else
      begin
         //inc the count
         inc(Info.bg_free_blocks_count);
         inc(Part.GetSuper.s_free_blocks_count);
      end;
      Part.SetDescDirty;
      Part.SetSuperDirty;
      Dirty := True;
   end;
end;


end.
