unit LinuxFS;

interface

uses Windows, ex2, Dialogs, SysUtils, WinIOCTL, Classes, Saving, BlockDev;

type

  TLinuxPartition = class
  public
    constructor Create(FileName : String; DriveNo : Integer; PartNo : Integer;
                        Offset : _Large_Integer; Length : _Large_Integer;
                        SectorSize : Integer; Prefix : String);
    destructor  Destroy; override;
    procedure   Sync;
    procedure   Format;
    procedure   Open;
    procedure   Close;

    function  Description : String;

    procedure DumpBlock(Block : Pointer);
    procedure DumpINode(INode : Pext2_inode);
    procedure DumpDescriptor(Group : Integer);
    procedure DumpSuperblock;

    function  GetSuper : Pext2_super_block;

    function GetBlockSize : USHORT;
    function GetSectorSize : USHORT;

    procedure LinkINode(DirectoryINode : ULONG; FileINode : ULONG; FileName : String);
    procedure UnLinkINode(DirectoryINode : ULONG; FileINode : ULONG);

    procedure DeleteINode(INode : ULONG);

   // used to create files.....
    procedure DeleteFile(ParentINode : ULONG; FileINode : ULONG);
    function  CreateFile(ParentINode : ULONG; FileName : String) : ULong;
    procedure CreateNode(ParentINode : ULONG; FileName : String; Sort : ULong; Major : Integer; Minor : Integer);
    procedure CreateSymLink(ParentINode : ULONG; FileName : String; Destination : String);

    function  MakeDirectory(ParentINode : ULONG; DirName : String) : ULong; // return the INode No
    procedure RmDirectory(ParentINode : ULONG; INode : ULONG);

    procedure Rename(ParentINode : ULONG; ChildINode : ULONG; NewName : String);
    procedure ImportFiles(ParentINode : ULONG; Directory : String);
   //

//////
    function GetInodeGroup(inode  : ULONG) : Integer;
    function GetGroupInfo(GroupNo : ULONG) : Pext2_group;


    function  LockBlockGroup(GroupNo : ULONG) : TObject;//BlockGroup;
    function  GetBlockGroup(GroupNo : ULONG) : TObject;//BlockGroup;
    procedure UnLockBlockGroup(GroupNo : ULONG);

    function  IndirectBlockCount : ULONG;

    function  AllocDataBlock(var GroupNo : ULONG) : ULONG;
    function  FindGroupFreeDataBlock : ULong;
    procedure UnAllocDataBlock(BlockNo : ULONG);

    function  AllocINode(var GroupNo : ULong) : ULong;
    function  FindGroupFreeINode : ULong;
    procedure UnAllocINode(INode : ULong);

    procedure FreeINodeBlocks(Info : Pext2_inode);

    function  BlockFromGroup(BlockNo : ULONG; Group : Pointer) : Pointer;

    function  GetBlockMem(Count : ULONG) : Pointer;
    function  GetBlock(BlockNo : ULONG; Count : ULONG) : Pointer;
    procedure FreeBlock(var Block : Pointer ; Count : ULONG);
    procedure SaveBlock(Block : Pointer; BlockNo : ULONG; Count : ULONG);

    function GetBlockHeadSize : ULong;

    procedure SetDescDirty;
    procedure SetSuperDirty;

    function GetFileName   : String;
    function GetLength     : _Large_Integer;

    procedure EnableWrite;
    function ReadWrite : Boolean;
  private
    IsOpen      : Boolean;
    // here is info on the partition which contains the file system
    Drive       : integer; // physical drive no (0 = 1st etc)
    Partition   : integer; // partition no -- though not used?
    FileName    : String;
    SectorSize  : DWORD;
    Start       : _Large_Integer; // start
    Length      : _Large_Integer; // length
    Writeable   : Boolean;
    Prefix      : String;
///////
    BlockDevice : TBlockDevice;

    Super      : ext2_super_block;   // the disk superblock
    SuperDirty : Boolean;            // has the superblock be modified?
    Desc       : Pext2_group;        // descriptor table
    DescSize   : Integer;            // size of descriptor table
    DescDirty  : Boolean;            // do they need to be written back?

    AllocatedBlocks  : ULONG;
    BlockMapSize     : ULONG; // Blocks needed for Block Bitmap
    INodeMapSize     : ULONG; // Blocks needed for INode Bitmap
    TableSize        : ULONG; // Blocks needed for INode table

    // Superblock info...
    BlockSize        : USHORT;//Integer; // in bytes
    SectorsPerBlock  : USHORT;//Integer;
    Groups           : ULONG;

    BlockHeadSize : ULONG;
    BlockGroups   : TList; // list of BlockGroup
//    DataBlocks    : TList;

    // sector cache
    SectorCache      : Pointer;
    SectorCacheStart : ULong;
    SectorCacheCount : ULong;
    SectorCacheMax   : ULong;

    procedure ReadSector(Sector : DWORD; count : DWORD; Buffer : Pointer);
    procedure WriteSector(Sector : DWORD; count : DWORD; Buffer : Pointer);

    procedure ReadPhysicalSector(Sector : DWORD; count : DWORD; Buffer : Pointer);
    procedure WritePhysicalSector(Sector : DWORD; count : DWORD; Buffer : Pointer);

    procedure SetBlockSize(Size : USHORT{Integer});

    procedure InitSectorCache(Count : ULong);
    function  CacheEntry(I : ULong) : Pointer;
    procedure CommitSectorCache;

  end;

implementation

uses ex2explore, binfile, time, INode, Blocks, Directory, Utils;

constructor TLinuxPartition.Create(FileName : String; DriveNo : Integer; PartNo : Integer;
                        Offset : _Large_Integer; Length : _Large_Integer;
                        SectorSize : Integer; Prefix : String);
begin
   inherited Create;

    self.Drive       := DriveNo;
    self.Partition   := PartNo;
    self.FileName    := FileName;
    self.SectorSize  := SectorSize;
    self.Start       := Offset;
    self.Length      := Length;
    self.Writeable   := False;
    self.Prefix      := Prefix;


   AllocatedBlocks   := 0;
   Desc              := nil;

   BlockDevice := nil;
   Writeable   := False;

   SectorCache := nil;
   IsOpen      := False;
end;

destructor TLinuxPartition.Destroy;
begin
   Close;
   if AllocatedBlocks <> 0 then
   begin
      MessageDlg('Error! ' + IntToStr(AllocatedBlocks) + ' blocks still allocated', mtError, [mbOK], 0);
   end;

   inherited Destroy;
end;

procedure TLinuxPartition.Open;
var
   i     : Integer;
begin
   // create the blockDevice object.
   // if this is 95 and we want to open a disk then
   // we have to create a 95 disk
   // otherwise go with the NT one

   if IsOpen = False then
   begin
      IsOpen := True;
      if not Assigned(BlockDevice) then
      begin
         if (Drive = -1) or (OSType = VER_PLATFORM_WIN32_NT) then
         begin
            BlockDevice := TNTDisk.Create;
            with BlockDevice as TNTDisk do
            begin
               SetFileName(FileName);
               SetMode(Writeable);
               SetPartition(Start, Length);
            end;
         end
         else
         begin
            BlockDevice := TWin95Disk.Create;
            with BlockDevice as TWin95Disk do
            begin
               SetDiskNumber(Drive);
               SetOffset(Start.Quadpart);
            end;
         end;

         if not BlockDevice.Open then
         begin
            MessageDlg('Error opening device err=' + IntToStr(GetLastError), mtError, [mbOK], 0);
            raise Exception.Create('Error opening device');
         end
         else
         try
            // The superblock is always 1024 bytes from start of partition.
            ReadSector(1024 div SectorSize , sizeof(Super) div SectorSize, @Super);

            if Super.s_magic = EXT2_SUPER_MAGIC then
            begin
               Debug('Found Valid Superblock', DebugMedium);
               // see what the block size should be
               case Super.s_log_block_size of
                  0 : // 1k block size
                     begin
                        Debug('1k block size', DebugMedium);
                        SetBlockSize(1024);
                     end;
                  1 : // change to 2k
                     begin
                        Debug('2k block size', DebugMedium);
                        SetBlockSize(2048);
                     end;
                  2 : // change to 4k
                     begin
                        Debug('4k block size', DebugMedium);
                        SetBlockSize(4096);
                     end;
               else
                  Debug('Unknown block size', DebugOff);
                  raise Exception.Create('Unknown block size');
               end;
            end
            else
            begin
               Debug('Could not find valid Superblock', DebugOff);
               raise Exception.Create('Could not find valid Superblock');
            end;

            begin
               DumpSuperBlock;

               // read info from superblock.....
               Groups := div2(Super.s_blocks_count, Super.s_blocks_per_group);
               Debug(IntToStr(Groups) + ' groups of ' + IntToStr(Super.s_blocks_per_group) + ' blocks', DebugMedium);

               // lock the descriptors...
               // how many blocks do we need?
               DescSize := div2((Groups * sizeof(ext2_group)), BlockSize);
               Debug('Descriptors take ' + IntToStr(DescSize) + ' blocks', DebugMedium);

               // we need to lock DescSize blocks.....

               Desc := GetBlock(Super.s_first_data_block + 1, DescSize);
               DescDirty := False;
               // DumpBlock(Desc);

               // blocks in bitmaps?
               BlockMapSize := div2(div2(Super.s_blocks_per_group, 8), BlockSize);
               Debug('Block bitmap ' + IntToStr(BlockMapSize) + ' blocks', DebugMedium);

               // bocks in inode bitmap
               INodeMapSize := div2(div2(Super.s_inodes_per_group, 8), BlockSize);
               Debug('inode bitmap ' + IntToStr(INodeMapSize) + ' blocks', DebugMedium);

               // how many blocks in an inode table?
               TableSize := div2(Super.s_inodes_per_group * sizeof(ext2_inode), BlockSize);
               Debug('inode table ' + IntToStr(TableSize) + ' blocks', DebugMedium);

               // Create the BlockGroups
               BlockHeadSize  := BlockMapSize + INodeMapSize + TableSize;
               BlockGroups    := TList.Create;
               for i := 0 to Groups - 1 do
               begin
                  BlockGroups.Add(nil); // They will be created when they are accessed
               end;
            end;
         except
            Close;
            raise;
         end;
      end;
   end;
end;

procedure TLinuxPartition.Format;
//var
//   i     : Integer;
begin
   // create the blockDevice object.
   // if this is 95 and we want to open a disk then
   // we have to create a 95 disk
   // otherwise go with the NT one

   if not Assigned(BlockDevice) then
   begin
      if (Drive = -1) or (OSType = VER_PLATFORM_WIN32_NT) then
      begin
         BlockDevice := TNTDisk.Create;
         with BlockDevice as TNTDisk do
         begin
            SetFileName(FileName);
            SetMode(Writeable);
            SetPartition(Start, Length);
         end;
      end
      else
      begin
         BlockDevice := TWin95Disk.Create;
         with BlockDevice as TWin95Disk do
         begin
            SetDiskNumber(Drive);
            SetOffset({Round}(Start.Quadpart));
         end;
      end;

      if not BlockDevice.Open then
      begin
         MessageDlg('Error opening device err=' + IntToStr(GetLastError), mtError, [mbOK], 0);
         raise Exception.Create('Error opening device');
      end
      else
      try
         IsOpen := True;
         // The superblock is always 1024 bytes from start of partition.

         // start by creating a Superblock

         //ReadSector(1024 div SectorSize , sizeof(Super) div SectorSize, @Super);
      finally
      end
   end
   else
   begin
      MessageDlg('Device is already open', mtError, [mbOK], 0);
   end;
end;

procedure TLinuxPartition.Sync;
var
   i : ULong;
begin
   if IsOpen then
   begin
      for i := 0 to Groups - 1 do
      begin
         if Assigned(BlockGroups[i]) then
         begin
            TBlockGroup(BlockGroups[i]).Sync;
            // We could free them, but I don't know what impact that would have
            // on performance and memory usage...
         end;
      end;

      if Assigned(Desc) then
      begin
         if DescDirty then
         begin
            // do we have to do this to every block group?
            for i := 1 to Groups do
            begin
               SaveBlock(Desc, (Super.s_blocks_per_group * (i - 1)) + (Super.s_first_data_block + 1), DescSize);
            end;
            DescDirty := False;
         end;
      end;

      // the first superblock is at offset 1024 in the partition

      // all the other superblocks (the backups) are in what is termed the first data block
      // at offset 0
      if SuperDirty then
      begin
         WriteSector(1024 div SectorSize , sizeof(Super) div SectorSize, @Super);

         for i := 2 to Groups do
         begin
            SaveBlock(@Super, (Super.s_blocks_per_group * (i - 1)) + Super.s_first_data_block, 1);
         end;
         SuperDirty := False;
      end;

      if Assigned(SectorCache) and (SectorCacheCount > 0) then
      begin
         CommitSectorCache;
      end;
   end;
end;

procedure TLinuxPartition.Close;
var
   i : ULong;
begin
   if IsOpen then
   begin
      for i := 0 to Groups - 1 do
      begin
         if Assigned(BlockGroups[i]) then
         begin
            TBlockGroup(BlockGroups[i]).Free;
         end;
      end;
      BlockGroups.Free;
      BLockGroups := nil;

      if Assigned(Desc) then
      begin
         if DescDirty then
         begin
            // do we have to do this to every block group?
            for i := 1 to Groups do
            begin
               SaveBlock(Desc, (Super.s_blocks_per_group * (i - 1)) + (Super.s_first_data_block + 1), DescSize);
            end;
         end;
         FreeBlock(Pointer(Desc), DescSize);
      end;
      Desc := nil;


      // the first superblock is at offset 1024 in the partition

      // all the other superblocks (the backups) are in what is termed the first data block
      // at offset 0
      if SuperDirty then
      begin
         WriteSector(1024 div SectorSize , sizeof(Super) div SectorSize, @Super);

         for i := 2 to Groups do
         begin
            SaveBlock(@Super, (Super.s_blocks_per_group * (i - 1)) + Super.s_first_data_block, 1);
         end;
      end;

      if Assigned(SectorCache) and (SectorCacheCount > 0) then
      begin
         CommitSectorCache;
      end;
      FreeMem(SectorCache);
      SectorCache := nil;

      if Assigned(BlockDevice) then
      begin
         BlockDevice.Close;
         BlockDevice.Free;
      end;
      BlockDevice := nil;
   end;
   IsOpen := False;
end;

function TLinuxPartition.Description : String;
begin
   if Drive >= 0 then
   begin // assume it is a partition
      // is this a floppy?
      if OSType = VER_PLATFORM_WIN32_NT then
      begin
         if (Pos('\\.\', FileName) > 0) and (Pos(':', FileName) > 0) then
         begin
            Result := 'fd' + Chr(drive + ord('a')) + IntToStr(Partition);
         end
         else
         begin
            Result := Prefix + Chr(drive + ord('a')) + IntToStr(Partition);
         end;
      end
      else
      begin
         if drive < $80 then
         begin
            begin
               Result := 'fd' + Chr(drive + ord('a')) + IntToStr(Partition);
            end
         end
         else
         begin
            Result := Prefix + Chr(drive - $80 + ord('a')) + IntToStr(Partition);
         end;
      end;
   end
   else
   begin
      Result := FileName;
   end;
end;

procedure TLinuxPartition.SetBlockSize(Size : USHORT);
begin
   BlockSize         := Size;
   SectorsPerBlock   := Size div SectorSize;
end;

function TLinuxPartition.GetBlockSize : USHORT;
begin
   Result := BlockSize;
end;


// device access functions

function TLinuxPartition.CacheEntry(I : ULong) : Pointer;
begin
   Result := @PChar(SectorCache)[I * SectorSize]
end;

procedure TLinuxPartition.ReadSector(Sector : DWORD; count : DWORD; Buffer : Pointer);
var
   SectorInCache : ULong;
begin
   //is the sector in the cache?
   if Assigned(SectorCache) then
   begin
      if (Sector >= SectorCacheStart) then
      begin
         SectorInCache := Sector - SectorCacheStart;
         if SectorInCache < SectorCacheCount then
         begin
            // is the whole count in the buffer
            if (SectorInCache + Count) <= SectorCacheCount then
            begin
               CopyMemory(Buffer, CacheEntry(SectorInCache), count * SectorSize);
               exit; // I know, single entry, single exit....
            end
            else
            begin
               // there is a partial overlap
               CommitSectorCache;
            end;
         end;
      end;
   end;
   ReadPhysicalSector(Sector, Count, Buffer);
end;

procedure TLinuxPartition.WriteSector(Sector : DWORD; count : DWORD; Buffer : Pointer);
begin
   // is this the next in cache
   if Count > 0 then
   begin
      if SectorCache = nil then
      begin
         InitSectorCache(2048);
      end;
      if Sector = (SectorCacheStart + SectorCacheCount) then
      begin
         if (SectorCacheCount + Count) <= SectorCacheMax then
         begin
            CopyMemory(CacheEntry(SectorCacheCount), Buffer, Count * SectorSize);
            SectorCacheCount := SectorCacheCount + Count;
            if SectorCacheCount >= SectorCacheMax then
            begin
               CommitSectorCache;
            end;
         end
         else
         begin
            if SectorCacheCount = 0 then
            begin
               // there are more than max sectors.
               // we will bypass the cache and write it
               // directly
               WritePhysicalSector(Sector, Count, Buffer);
            end
            else
            begin
               CommitSectorCache;
               WriteSector(Sector, Count, Buffer);
            end;
         end;
      end
      else
      begin
         if SectorCacheCount = 0 then
         begin
            SectorCacheStart := Sector;
            WriteSector(Sector, Count, Buffer);
         end
         else
         begin
            CommitSectorCache;
            WriteSector(Sector, Count, Buffer);
         end;
      end;
   end;
end;

procedure TLinuxPartition.CommitSectorCache;
begin
   WritePhysicalSector(SectorCacheStart, SectorCacheCount, SectorCache);
   SectorCacheStart := 0;
   SectorCacheCount := 0;
end;

procedure TLinuxPartition.InitSectorCache(Count : ULong);
begin
   GetMem(SectorCache, Count * SectorSize);
   SectorCacheMax    := Count;
   SectorCacheStart  := 0;
   SectorCacheCount  := 0;
end;


procedure TLinuxPartition.ReadPhysicalSector(Sector : DWORD; count : DWORD; Buffer : Pointer);
begin
   if Assigned(BlockDevice) then
   begin
      BlockDevice.ReadPhysicalSector(Sector, Count, Buffer);
   end;
end;

procedure TLinuxPartition.WritePhysicalSector(Sector : DWORD; count : DWORD; Buffer : Pointer);
begin
   if Assigned(BlockDevice) then
   begin
      BlockDevice.WritePhysicalSector(Sector, Count, Buffer);
   end;
end;

// Block management

function TLinuxPartition.GetBlockMem(Count : ULONG) : Pointer;
begin
   GetMem(Result, BlockSize * Count);
   AllocatedBlocks := AllocatedBlocks + Count;
end;

function TLinuxPartition.GetBlock(BlockNo : ULONG; Count : ULONG) : Pointer;
var
   StartSector : DWORD;
begin
   if Assigned(BlockDevice) then
   begin
      // allocate block of mem
      GetMem(Result, BlockSize * Count);

      StartSector := BlockNo * SectorsPerBlock;

      Debug(' block ' + IntToStr(BlockNo) + ' starts at sector no ' + IntToStr(StartSector), DebugHigh);

      ReadSector(StartSector , SectorsPerBlock * Count, Result);
      AllocatedBlocks := AllocatedBlocks + Count;
   end
   else
   begin
      Result := nil;
   end;
end;

procedure TLinuxPartition.FreeBlock(var Block : Pointer; Count : ULONG);
begin
   AllocatedBlocks := AllocatedBlocks - Count;
   if assigned(Block) then
   begin
      FreeMem(Block);
      Block := nil;
   end;
end;

procedure TLinuxPartition.SaveBlock(Block : Pointer; BlockNo : ULONG; Count : ULONG);
var
   StartSector : DWORD;
begin
   if Assigned(BlockDevice) then
   begin
      // allocate block of mem
      StartSector := BlockNo * SectorsPerBlock;

      Debug('Saving block ' + IntToStr(BlockNo) + ' starts at sector no ' + IntToStr(StartSector), DebugHigh);

      WriteSector(StartSector , SectorsPerBlock * Count, Block);
   end
   else
   begin
      raise Exception.Create('Write failed!, invalid Handle');
   end;
end;

// the group that the inode can be found in
function TLinuxPartition.GetInodeGroup(inode : ULONG) : Integer;
var
   GroupNo  : Integer;
begin
   GroupNo := ((inode - 1) div Super.s_inodes_per_group) + 1;
   Result  := GroupNo;
end;


// returns a pointer into the group that the block(in that group) is at
function TLinuxPartition.BlockFromGroup(BlockNo : ULONG; Group : Pointer) : Pointer;
begin
   Result := @(PChar(Group)[BlockSize * (BlockNo - 1)]);
end;
// Note on this pointer notation:
// It is only possible (at least in old versions of Delphi) to do pointer
// arrithamitic on PChar's.
// the notation @(PChar(pointer a)[offset]) calculates the address of
// pointer a + offset in bytes.
// There are probably better ways of doing this, such as typed pointers
// but at the moment, I don't want to break any of the existing code


function TLinuxPartition.GetSuper : Pext2_super_block;
begin
   Result := @Super;
end;

// resurns a pointer to the group descriptor for a particular group
function TLinuxPartition.GetGroupInfo(GroupNo : ULONG) : Pext2_group;
var
   Offset : DWord;
begin
   Offset := sizeof(ext2_group) * (GroupNo - 1);
   Result := @PChar(Desc)[Offset];
end;


// Then number of block pointers which can be stored in a block
function TLinuxPartition.IndirectBlockCount : ULONG;
begin
   Result := Blocksize div sizeof(ULONG);
end;

function TLinuxPartition.LockBlockGroup(GroupNo : ULONG) : TObject;
var
   i : Integer;
begin
   if (GroupNo > 0) and (GroupNo <= Groups) then
   begin
      i := GroupNo - 1;
      if not Assigned(BlockGroups[i]) then
      begin
         Debug('LockBlockGroup ' + IntToStr(GroupNo), DebugMedium);
         BlockGroups[i] := TBlockGroup.Create(self, GroupNo);
      end;
   end
   else
   begin
      raise Exception.Create('lock attempt on non-existant group');
   end;
   // now the BlockGroup is in the array
   TBlockGroup(BlockGroups[i]).IncLockCount;
   Result := BlockGroups[i];
end;

function TLinuxPartition.GetBlockGroup(GroupNo : ULONG) : TObject;
begin
   Result := BlockGroups[GroupNo - 1];
end;

procedure TLinuxPartition.UnLockBlockGroup(GroupNo : ULONG);
var
   i : Integer;
begin
   if (GroupNo > 0) and (GroupNo <= Groups) then
   begin
      i := GroupNo - 1;
      Debug('UnLockBlockGroup ' + IntToStr(GroupNo), DebugMedium);
      TBlockGroup(BlockGroups[i]).DecLockCount;

      // if lock count = 0 we could free it...
   end
   else
   begin
      raise Exception.Create('unlock attempt on non-existant group');
   end;
end;

// Dump procedures for debugging

procedure TLinuxPartition.DumpBlock(Block : Pointer);
var
   S  : String;
   S2 : String;
   i  : Integer;
begin
   Debug('Block dump  size = ' + IntToStr(GetBlockSize), DebugHigh);
   S := '0000 ';
   for i := 0 to GetBlockSize - 1 do
   begin
      S := S + IntToHex(Ord(PChar(Block)[i]), 2);
      if not (ord(PChar(Block)[i]) in [0, 8, 9]) then
      begin
         S2 := S2 + PChar(Block)[i];
      end
      else
      begin
         S2 := S2 + ' ';
      end;
      S := S + ' ';
      if System.Length(S) >= 52 then
      begin
         Debug(S + ' ' + S2, DebugHigh);
         S := IntToHex(i + 1, 4) + ' ';
         s2 := '';
      end;
   end;
end;

procedure TLinuxPartition.DumpDescriptor(Group : Integer);
var
   Info : Pext2_group;
begin
   // get a pointer to the descriptor for this group
   Info := GetGroupInfo(Group);
   Debug('Group Descriptor info for group ' + IntToStr(Group), DebugHigh);
   Debug('block bitmap block ' + IntToStr(Info.bg_block_bitmap), DebugHigh);
   Debug('inode bitmap block ' + IntToStr(Info.bg_inode_bitmap), DebugHigh);
   Debug('inode table block  ' + IntToStr(Info.bg_inode_table), DebugHigh);
   Debug('free blocks        ' + IntToStr(Info.bg_free_blocks_count), DebugHigh);
   Debug('free inodes        ' + IntToStr(Info.bg_free_inodes_count), DebugHigh);
   Debug('used dirs          ' + IntToStr(Info.bg_used_dirs_count), DebugHigh);
end;

procedure TLinuxPartition.DumpSuperblock;
begin
   Debug('Inodes count          : ' + IntToStr(Super.s_inodes_count), DebugMedium);
   Debug('Blocks count          : ' + IntToStr(Super.s_blocks_count), DebugMedium);
   Debug('Reserved blocks count : ' + IntToStr(Super.s_r_blocks_count), DebugMedium);
   Debug('Free blocks count     : ' + IntToStr(Super.s_free_blocks_count), DebugMedium);
   Debug('Free inodes count     : ' + IntToStr(Super.s_free_inodes_count), DebugMedium);
   Debug('First Data Block      : ' + IntToStr(Super.s_first_data_block), DebugMedium);
   Debug('Block size            : ' + IntToStr(Super.s_log_block_size), DebugMedium);
   Debug('# Blocks per group    : ' + IntToStr(Super.s_blocks_per_group), DebugMedium);
   Debug('# Fragments per group : ' + IntToStr(Super.s_frags_per_group), DebugMedium);
   Debug('# Inodes per group    : ' + IntToStr(Super.s_inodes_per_group), DebugMedium);
   Debug('Mount time            : ' + IntToStr(Super.s_mtime), DebugMedium);
   Debug('Write time            : ' + IntToStr(Super.s_wtime), DebugMedium);
   Debug('Mount count           : ' + IntToStr(Super.s_mnt_count), DebugMedium);
   Debug('Maximal mount count   : ' + IntToStr(Super.s_max_mnt_count), DebugMedium);
   Debug('File system state     : ' + IntToStr(Super.s_state), DebugMedium);
   Debug('Behaviour when detecting errors   : ' + IntToStr(Super.s_errors), DebugMedium);
   Debug('time of last check                : ' + IntToStr(Super.s_lastcheck), DebugMedium);
   Debug('max. time between checks          : ' + IntToStr(Super.s_checkinterval), DebugMedium);
   Debug('OS                    : ' + IntToStr(Super.s_creator_os), DebugMedium);
   Debug('Revision level        : ' + IntToStr(Super.s_rev_level), DebugMedium);
end;

procedure TLinuxPartition.DumpINode(INode : Pext2_inode);
var
   i : Integer;
   S : String;
begin
   Debug('INode mode        : ' + IntToHex(INode.i_mode,4), DebugHigh);
   Debug('INode size        : ' + IntToStr(INode.i_size), DebugHigh);
   Debug('INode uid         : ' + IntToStr(INode.i_uid), DebugHigh);
   Debug('INode gid         : ' + IntToStr(INode.i_gid), DebugHigh);
   Debug('access time       : ' + IntToStr(INode.i_atime), DebugHigh);
   Debug('creation time     : ' + IntToStr(INode.i_ctime), DebugHigh);
   Debug('modification time : ' + IntToStr(INode.i_mtime), DebugHigh);
   Debug('delection time    : ' + IntToStr(INode.i_dtime), DebugHigh);
   Debug('link count        : ' + IntToStr(INode.i_links_count), DebugHigh);
   Debug('blocks            : ' + IntToStr(INode.i_blocks), DebugHigh);
   Debug('flags             : ' + IntToHex(INode.i_flags, 8), DebugHigh);

   for i := 1 to EXT2_NDIR_BLOCKS do
   begin
      S := S + IntToStr(INode.i_block[i]) + ' ';
   end;
   Debug('direct blocks     : ' + S, DebugHigh);
   Debug('indirect          : ' + IntToStr(INode.i_block[EXT2_IND_BLOCK]), DebugHigh);
   Debug('double indirect   : ' + IntToStr(INode.i_block[EXT2_DIND_BLOCK]), DebugHigh);
   Debug('tripple indirect  : ' + IntToStr(INode.i_block[EXT2_TIND_BLOCK]), DebugHigh);

end;

// This functions finds a free inode on the disk, prefferably in the
// specified group.  Group 0 means there is no prefered group

function TLinuxPartition.AllocINode(var GroupNo : ULong) : ULong;
var
   Group       : TBlockGroup;
   Info        : Pext2_group;
begin
   if GroupNo = 0 then // look up a group
   begin
      GroupNo := FindGroupFreeINode;
   end
   else
   begin
      // check if there is an available INode in the requested block...
      Info := GetGroupInfo(GroupNo);
      if Info.bg_free_inodes_count = 0 then // we have to find a new one
      begin
         GroupNo := FindGroupFreeINode;
      end;
   end;

   // GroupNo is now valid and has a free INode
   Group   := TBlockGroup(LockBlockGroup(GroupNo));
   Result  := Group.AllocNewINode;

   Result := (Result + 1) + ((GroupNo - 1) * Super.s_inodes_per_group);

   // we should lock the INode and return that?
   // (so that the the BlockGroup is not released before we can assign data to the inode?)

   UnLockBlockGroup(GroupNo);
end;

function  TLinuxPartition.FindGroupFreeINode : ULong;
var
   DestGroup   : ULong;
   DestCount   : ULong;
   Info        : Pext2_group;
   i           : Integer;

   // return a GroupNo that contains a free INode
begin

   Info := GetGroupInfo(1);
   DestGroup := 1;
   DestCount := Info.bg_free_inodes_count;

   for i := 1 to Groups do
   begin
      Info := GetGroupInfo(i);
      Debug('Group ' + IntToStr(i), DebugHigh);
      Debug('Free INodes ' + IntToStr(Info.bg_free_inodes_count), DebugHigh);
      if Info.bg_free_inodes_count > DestCount then
      begin
         DestGroup := i;
         DestCount := Info.bg_free_inodes_count;
      end;
   end;
   Debug('Group ' + IntToStr(DestGroup) + ' wins', DebugHigh);
   Result := DestGroup;
end;

// Unallocate an inode
procedure TLinuxPartition.UnAllocINode(INode : ULong);
var
   GroupNo      : ULONG;
   INodeInGroup : ULONG;
begin
   GroupNo      := GetInodeGroup(INode);
   INodeInGroup := (INode - 1) mod Super.s_inodes_per_group;

   // SetINodeBit should take care of all necessary counters
   TBlockGroup(LockBlockGroup(GroupNo)).SetINodeBit(INodeInGroup, False);
   UnLockBlockGroup(GroupNo);
end;

function TLinuxPartition.AllocDataBlock(var GroupNo : ULONG) : ULONG;
var
   Group       : TBlockGroup;
   Info        : Pext2_group;
begin
   if GroupNo = 0 then // look up a group
   begin
      GroupNo := FindGroupFreeDataBlock;
   end
   else
   begin
      // check if there is an available INode in the requested block...
      Info := GetGroupInfo(GroupNo);
      if Info.bg_free_blocks_count = 0 then // we have to find a new one
      begin
         GroupNo := FindGroupFreeDataBlock;
      end;
   end;

   // GroupNo is now valid and has a free INode
   Group   := TBlockGroup(LockBlockGroup(GroupNo));
   Result  := Group.AllocNewBlock;

   // xlate result into block number, rather than block in group
   Result := (Result + 1) + ((GroupNo - 1) * Super.s_blocks_per_group);

   UnLockBlockGroup(GroupNo);
end;

procedure TLinuxPartition.UnAllocDataBlock(BlockNo : ULONG);
var
   GroupNo      : ULONG;
   BlockInGroup : ULONG;
begin
   GroupNo := ((BlockNo - 1) div Super.s_blocks_per_group) + 1;
   BlockInGroup := (BlockNo - 1) mod Super.s_blocks_per_group;

   TBlockGroup(LockBlockGroup(GroupNo)).SetBlockBit(BlockInGroup, False);
   UnLockBlockGroup(GroupNo);
end;

function TLinuxPartition.FindGroupFreeDataBlock : ULONG;
var
   DestGroup   : ULong;
   DestCount   : ULong;
   Info        : Pext2_group;
   i           : Integer;

   // return a GroupNo that contains a free Block
begin

   Info := GetGroupInfo(1);
   DestGroup := 1;
   DestCount := Info.bg_free_blocks_count;

   for i := 1 to Groups do
   begin
      Info := GetGroupInfo(i);
      Debug('Group ' + IntToStr(i), DebugHigh);
      Debug('Free Blocks ' + IntToStr(Info.bg_free_blocks_count), DebugHigh);
      if Info.bg_free_blocks_count > DestCount then
      begin
         DestGroup := i;
         DestCount := Info.bg_free_blocks_count;
      end;
   end;
   Debug('Group ' + IntToStr(DestGroup) + ' wins', DebugHigh);
   Result := DestGroup;
end;

procedure TLinuxPartition.DeleteFile(ParentINode : ULONG; FileINode : ULONG);
var
   Child  : TInode;
begin
   UnLinkINode(ParentINode, FileINode);
   Child := TINode.Create(self, FileINode);
   try
      if Child.Info.i_links_count = 0 then
      begin
         DeleteINode(FileINode);
      end
   finally
      Child.Free;
   end;
end;

procedure TLinuxPartition.LinkINode(DirectoryINode : ULONG; FileINode : ULONG; FileName : String);
var
   Directory   : TDirectory;
   INode       : TINode;
begin
   // create a TDirectory to do it for us.....
   INode := TINode.Create(self, DirectoryINode);
   Directory := TDirectory.Create(self, INode);
   try
      // this updates the directory on disk.  No caching is done
      Directory.Link(FileINode, ExtractFileName(FileName));
   finally
      // Does this order matter?
      INode.Free;
      Directory.Free;
   end;
   INode := TINode.Create(self, FileINode);
   try
      INode.info.i_links_count := INode.info.i_links_count + 1;
      INode.Dirty := True;
   finally
      INode.Free;
   end;
end;

procedure TLinuxPartition.UnLinkINode(DirectoryINode : ULONG; FileINode : ULONG);
var
   Parent : TINode;
   Dir    : TDirectory;
   Child  : TInode;
begin
   Parent := TINode.Create(self, DirectoryINode);
   try
      Dir := TDirectory.Create(self, Parent);
      try
         Dir.UnLink(FileINode);
      finally
         Dir.Free;
      end;
   finally
      Parent.Free;
   end;

   Child := TINode.Create(self, FileINode);
   try
      // adjust ref count
      Dec(Child.Info.i_links_count);
      TBlockGroup(GetBlockGroup(Child.GroupNo)).Dirty := True;
   finally
      Child.Free;
   end;
end;

procedure TLinuxPartition.DeleteINode(INode : ULONG);
var
   Child  : TInode;
begin
   // look at the parent inode
   Child := TINode.Create(self, INode);
   try
      if Child.Info.i_links_count = 0 then
      begin
         // set the delete time =
         Child.Info.i_dtime := CurrentCTime;

         // free data blocks
         FreeINodeBlocks(Child.Info);

         // free inode
         UnAllocINode(INode);
      end
      else
      begin
         Debug('Deleting INode with link count > 0', DebugOff);
      end;
   finally
      Child.Free;
   end;
end;


//test these functions!!!!!!!!!!

procedure TLinuxPartition.CreateNode(ParentINode : ULONG; FileName : String; Sort : ULong; Major : Integer; Minor : Integer);
var
   INode    : ULong;
   INodeO   : TINode;
   DeviceNo : ULong;
begin
   // Create a file and then change it into a device.... :)
   DeviceNo := ((Major shl 8) and $FF00) or (Minor and $FF);
   INode := CreateFile(ParentINode, FileName);
   INodeO := TINode.Create(self, INode);
   try
      if Sort = S_IFBLK then
      begin
         INodeO.info.i_mode := S_IFBLK or CreateFlags;
         INodeO.info.i_block[1] := DeviceNo;
         // the the device no.
      end
      else if Sort = S_IFCHR then
      begin
         INodeO.info.i_mode := S_IFCHR or CreateFlags;
         INodeO.info.i_block[1] := DeviceNo;
      end;
   finally
      INodeO.Free;
   end;
end;

procedure TLinuxPartition.CreateSymLink(ParentINode : ULONG; FileName : String; Destination : String);
var
   INode    : ULong;
   INodeO   : TINode;
   BlockO   : TBlock;
begin
   // Create a file and then change it into a link.... :)
   INode := CreateFile(ParentINode, FileName);
   INodeO := TINode.Create(self, INode);
   try
      INodeO.info.i_mode := USHORT(S_IFLNK or S_IRWXU or S_IRWXG or S_IRWXO);

      if System.Length(Destination) > (EXT2_N_BLOCKS * sizeof(ULONG)) then
      begin
         // we need a data block
         INodeO.info.i_block[1] := AllocDataBlock(INodeO.GroupToUse);
         BlockO := TBlock.Create(self, INodeO.info.i_block[1], False);
         try
            CopyMemory(BlockO.Block, PChar(Destination), System.Length(Destination));
            BlockO.Dirty := True;
         finally
            BlockO.Free;
         end;
      end
      else
      begin
         // copy the destination onto the block info
         CopyMemory(@INodeO.info.i_block[1], PChar(Destination), System.Length(Destination));
      end;
      INodeO.info.i_size := System.Length(Destination);
   finally
      INodeO.Free;
   end;
end;


procedure TLinuxPartition.FreeINodeBlocks(info : Pext2_inode);
var
   Processed : ULONG;
   i         : Integer;

   procedure ProcessBlock(BlockNo : ULONG; Indirection : Integer);
   var
      Block       : TBlock;
      iBlockNo    : Integer;
      i           : Integer;
   begin
      if BlockNo > 0 then
      begin
         if Indirection = 0 then // this block is data
         begin
            UnAllocDataBlock(BlockNo);
            Processed := Processed + GetBlockSize;
         end
         else
         begin // recurse with this indirect block
            Block := TBlock.Create(self, BlockNo, True);
            try
               for i := 1 to IndirectBlockCount do
               begin
                  iBlockNo := Block.GetIndirectBlockNo(i);
                  ProcessBlock(iBlockNo, Indirection - 1);
                  if (Processed >= info.i_size) then break;
               end;
            finally
               Block.Free;
               UnAllocDataBlock(BlockNo);
            end;
         end;
      end;
   end;

begin
   // make sure the list is empty

   Processed  := 0;

   try
      for i := 1 to EXT2_NDIR_BLOCKS do
      begin
         if info.i_block[i] > 0 then
         begin
            ProcessBlock(info.i_block[i], 0);
            if (Processed >= info.i_size) then break;
         end;
      end;

      if (Processed < info.i_size) then
      begin
         ProcessBlock(info.i_block[EXT2_IND_BLOCK], 1);
      end;

      if (Processed < info.i_size) then
      begin
         ProcessBlock(info.i_block[EXT2_DIND_BLOCK], 2);
      end;

      if (Processed < info.i_size) then
      begin
         ProcessBlock(info.i_block[EXT2_TIND_BLOCK], 3);
      end;

      // 0 the entry
      for i := 1 to EXT2_N_BLOCKS do
      begin
         info.i_block[i] := 0;
      end;
   finally
   end;
end;

function TLinuxPartition.CreateFile(ParentINode : ULONG; FileName : String) : ULong;
   // allocate an inode
   // link the file name with the parent dir.

   // return the INode No.
var
   INode : ULong;
   InodeO: TINode;
   Group : ULong;
begin
   Result := 0;
   if ParentINode > 0 then
   begin
      Group := GetINodeGroup(ParentINode);
   end
   else
   begin
      Group := 0;
   end;

   try
      INode := AllocINode(Group);
      Result := INode;
      Debug('New INode ' + IntToStr(INode), DebugMedium);
      INodeO := TINode.Create(self, INode);
      INodeO.Initialise(S_IFREG);
      INodeO.Free;
      try
         // Now link file into directory.....
         LinkINode(ParentINode, INode, FileName);
      except
         Debug('Error linking INode, deleting it...', DebugOff);
         UnLinkINode(ParentINode, INode);
      end;
   except
      on E : Exception do
      begin
         Debug('Error allocating new INode: ' + E.Message, DebugOff);
      end;
   end
end;

function TLinuxPartition.GetBlockHeadSize : ULong;
begin
   Result := BlockHeadSize;
end;

procedure TLinuxPartition.SetDescDirty;
begin
   DescDirty  := True;
end;

procedure TLinuxPartition.SetSuperDirty;
begin
   SuperDirty := True;
end;

function TLinuxPartition.GetFileName : String;
begin
   Result := FileName;
end;

function TLinuxPartition.GetLength : _Large_Integer;
begin
   Result := Length;
end;

procedure TLinuxPartition.EnableWrite;
begin
   Writeable := True;
end;

function TLinuxPartition.GetSectorSize : USHORT;
begin
   Result := SectorSize;
end;

function TLinuxPartition.MakeDirectory(ParentINode : ULONG; DirName : String) : ULong;
var
   INode : ULong;
   InodeO: TINode;
   Group : ULong;
   Dir         : TStringList;
   Directory   : TDirectory;
begin
   if ParentINode > 0 then
   begin
      Group := GetINodeGroup(ParentINode);
   end
   else
   begin
      Group := 0;
   end;
   Debug('Using group ' + IntToStr(Group), DebugHigh);
   INode := AllocINode(Group);
   Result := INode;
   Debug('New INode ' + IntToStr(INode), DebugMedium);
   INodeO := TINode.Create(self, INode);

   INodeO.Initialise(S_IFDIR);

   Dir := TStringList.Create;
   Dir.AddObject('.', Pointer(INode));
   Dir.AddObject('..', Pointer(ParentINode));

   Directory := TDirectory.Create(self, INodeO);
   try
      Directory.ReCreate(Dir);
   finally
      Directory.Free;
   end;
   Dir.Free;

   GetGroupInfo(INodeO.GroupNo).bg_used_dirs_count := GetGroupInfo(INodeO.GroupNo).bg_used_dirs_count + 1;
   INodeO.info.i_links_count := INodeO.info.i_links_count + 1;
   INodeO.Free;

   // Now link directory into parent directory.....
   LinkINode(ParentINode, INode, DirName);

   // adjust the link counts for . and ..
   INodeO := TINode.Create(self, ParentINode);
   INodeO.info.i_links_count := INodeO.info.i_links_count + 1;
   INodeO.Free;
end;

procedure TLinuxPartition.RmDirectory(ParentINode : ULONG; INode : ULong);

   function DoRm(INode : ULong) : Boolean; // return True if INode was a Directory
   var
      I           : Integer;
      INodeO      : TINode;
      Directory   : TDirectory;
      Dir         : TStringList;
   begin
      Result := False; // assume a file
      INodeO := TINode.Create(self, INode);
      try
         if (INodeO.info.i_mode and S_IFDIR) = S_IFDIR then
         begin
            Result := True;
            Directory := TDirectory.Create(self, INodeO);
            try
               Dir := TStringList.Create;
               Directory.EnumDir(Dir);
               for i := 0 to Dir.Count - 1 do
               begin
                  if not ((CompareStr(Dir[i], '.') = 0) or (CompareStr(Dir[i], '..') = 0)) then
                  begin
                     if DoRm(ULong(Dir.Objects[i])) then // True = directory
                     begin
                        INodeO.info.i_links_count := INodeO.info.i_links_count - 1;
                        INodeO.Dirty := True;
                     end;
                  end;
               end;
               Dir.Free;
            finally
               Directory.Free;
            end;
            INodeO.info.i_links_count := INodeO.info.i_links_count - 2; // 1 for for the '.' and 1 for the parent link to us
            INodeO.Dirty := True;
            Debug('Directory link count is ' + IntToStr(INodeO.info.i_links_count), DebugLow);
            // this should always be deleted, as you can't have a hard like do a dir?
            GetGroupInfo(INodeO.GroupNo).bg_used_dirs_count := GetGroupInfo(INodeO.GroupNo).bg_used_dirs_count - 1;
            self.SetDescDirty;
            DeleteINode(INode);
         end
         else
         begin // this is a file, but don't bother updating the parent dir.....
            INodeO.info.i_links_count := INodeO.info.i_links_count - 1;
            if INodeO.info.i_links_count = 0 then
            begin
               DeleteINode(INode);
            end;
            INodeO.Dirty := True;
         end;
      finally
         INodeO.Free;
      end;
   end;
var
   INodeO      : TINode;
   Directory   : TDirectory;
begin
   // look at the dir
   // for all the files, delete them
   // delete the dir
   // simple?
   DoRm(INode);

   // now unlink the dir from the parent
   INodeO := TINode.Create(self, ParentINode);
   Directory := TDirectory.Create(self, INodeO);
   Directory.Unlink(INode);
   Directory.Free;
   // and dec the link count (for ..)
   INodeO.info.i_links_count := INodeO.info.i_links_count - 1;
   INodeO.Dirty := True;
   INodeO.Free;
end;

procedure TLinuxPartition.Rename(ParentINode : ULONG; ChildINode : ULONG; NewName : String);
var
   InodeO   : TINode;
   Directory : TDirectory;
begin
   INodeO := TINode.Create(self, ParentINode);
   try
      Directory := TDirectory.Create(self, INodeO);
      try
         Directory.Rename(ChildINode, NewName);
      finally
         Directory.Free;
      end;
   finally
      INodeO.Free;
   end;
end;

function TLinuxPartition.ReadWrite : Boolean;
begin
   Result := Writeable;
end;

procedure TLinuxPartition.ImportFiles(ParentINode : ULONG; Directory : String);
var
   FileList : TFileList;
   i        : Integer;
   NewINode : ULong;
   Name     : String;
   INodeO   : TINode;
begin
   FileList := TFileList.Create;
   FileList.SetAttrMask(faAnyFile);
   FileList.Calculate(Directory + '\*.*');
   for i := 0 to FileList.Count - 1 do
   begin
      Name := FileList.Get(i).Name;
      if FileList.Get(i).IsDirectory then
      begin
         if not((CompareStr(Name, '.') = 0) or (CompareStr(Name, '..') = 0)) then
         begin
            Debug('Directory : ' + FileList.Get(i).Name, DebugMedium);
            NewINode := MakeDirectory(ParentINode, FileList.Get(i).Name);
            ImportFiles(NewINode, Directory + '\' + Name);
         end;
      end
      else if FileList.Get(i).IsFile then
      begin
         Debug('File : ' + FileList.Get(i).Name, DebugMedium);
         NewINode := CreateFile(ParentINode, FileList.Get(i).Name);
         INodeO := TINode.Create(self, NewINode);
         try
            INodeO.ReadFromFile(Directory + '\' + FileList.Get(i).Name, nil, false);
         finally
            INodeO.Free;
         end;
      end;
   end;
   FileList.Free;
end;


end.
