unit Directory;

interface

uses LinuxFS, Windows, ex2, Saving, SysUtils, Classes, INode;

type
  TDirectory = class
  public

    constructor Create(Part : TLinuxPartition; INode : TINode);
    destructor Destroy; override;

    procedure EnumDir(List : TStringList);
    procedure ReCreate(List : TStringList);
    procedure UnLink(INode : ULong);
    procedure Link(INode : ULong; FileName : String);

    procedure Rename(INode : ULong; NewName : String);
  private
    INodeO     : TINode;
    Info       : Pext2_inode;
    Part       : TLinuxPartition;
  end;


implementation

uses Blocks, ex2explore;

///////////////////////////////////
// TDirectory
///////////////////////////////////

constructor TDirectory.Create(Part : TLinuxPartition; INode : TINode);
begin
   self.Part   := Part;
   INodeO      := INode;
   Info        := INode.Info;
end;

destructor TDirectory.Destroy;
begin
end;

procedure TDirectory.EnumDir(List : TStringList);
var
   Written  : ULONG;
   i        : Integer;

   procedure ProcessBlock(BlockNo : ULONG; Indirection : Integer);
   var
      Block       : TBlock;
      BlockOffset : Integer;
      Entry       : Pext2_dir_entry;
      iBlockNo    : Integer;
      Name        : String;
      i           : Integer;
   begin
      if Indirection = 0 then // this block is data
      begin
         // lock then block
         Block := TBlock.Create(Part, BlockNo, True);
         try
            // Block.Block is the data
            BlockOffset := 0;
            while BlockOffset < Part.GetBlockSize do
            begin
               Entry := Pext2_dir_entry(@PChar(Block.Block)[BlockOffset]);
               SetString(Name, Entry.name, Entry.name_len);
               Debug('INode    ' + IntToStr(Entry.INode), DebugHigh);
               Debug('rec len  ' + IntToStr(Entry.rec_len), DebugHigh);
               Debug('name len ' + IntToStr(Entry.name_len), DebugHigh);
               Debug('name     ' + Name + ' ' + IntToStr(Ord(Entry.name[Entry.name_len])), DebugHigh);
//               Debug(Name + ' ' + IntToStr(Entry.INode), DebugHigh);
               if (Length(Name) > 0) and (Entry.INode > 0) then
               begin
                  List.AddObject(Name, Pointer(Entry.INode));
               end;
               BlockOffset := BlockOffset + Entry.rec_len;
            end;
            Written := Written + Part.GetBlockSize;
         finally
            Block.Free;
         end;
      end
      else
      begin // recurse with this indirect block
         Block := TBlock.Create(Part, BlockNo, True);
         try
            for i := 1 to Part.IndirectBlockCount do
            begin
               iBlockNo := Block.GetIndirectBlockNo(i);
               ProcessBlock(iBlockNo, Indirection - 1);
               if (Written >= info.i_size) then break;
            end;
         finally
            Block.Free;
         end;
      end;
   end;

begin
   // make sure the list is empty
   List.Clear;

   Written  := 0; // the is processed, but we copied the code from save inode to file...

   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 (Written >= info.i_size) then break;
         end;
      end;

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

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

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

procedure TDirectory.UnLink(INode : ULong);
// remove the entry with this INode
var
   Processed : ULONG;
   i         : Integer;

   procedure ProcessBlock(BlockNo : ULONG; Indirection : Integer);
   var
      Block       : TBlock;
      BlockOffset : Integer;
      Entry       : Pext2_dir_entry;
      iBlockNo    : Integer;
      i           : Integer;
      PrevEntry   : Pext2_dir_entry;
   begin
      if Indirection = 0 then // this block is data
      begin
         // lock then block
         Block := TBlock.Create(Part, BlockNo, True);
         try
            // Block.Block is the data
            BlockOffset := 0;
            PrevEntry := nil;
            while BlockOffset < Part.GetBlockSize do
            begin
               Entry := Pext2_dir_entry(@PChar(Block.Block)[BlockOffset]);
               if Entry.inode = INode then
               begin
                  // this is the trick
                  // adjust the length of the PrevEntry so that it takes up this entry.
                  if Assigned(PrevEntry) then
                  begin
                     PrevEntry.rec_len := PrevEntry.rec_len + Entry.rec_len; // this should do it?
                     // tag the block for writing
                     Block.Dirty := True;
                     Processed := info.i_size;
                     break;
                  end
                  else // if it is the first in a block then?
                  begin
                     // just zero the inode....
                     Entry.inode := 0;
                     Block.Dirty := True;
                  end;
               end
               else
               begin
                  PrevEntry := Entry;
               end;
               BlockOffset := BlockOffset + Entry.rec_len;
            end;
            Processed := Processed + Part.GetBlockSize;
         finally
            Block.Free;
         end;
      end
      else
      begin // recurse with this indirect block
         Block := TBlock.Create(Part, BLockNo, True);
         try
            for i := 1 to Part.IndirectBlockCount do
            begin
               iBlockNo := Block.GetIndirectBlockNo(i);
               ProcessBlock(iBlockNo, Indirection - 1);
               if (Processed >= info.i_size) then break;
            end;
         finally
            Block.Free;
         end;
      end;
   end;

begin
   // make sure the list is empty

   Processed  := 0; // the is processed, but we copied the code from save inode to file...

   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;
   finally
   end;
end;

procedure TDirectory.Link(INode : ULong; FileName : String);
var
   Dir : TStringList;
begin
   Dir := TStringList.Create;
   try
      EnumDir(Dir);
      Dir.AddObject(FileName, Pointer(INode));
      Recreate(Dir);
      // inc the inode ref count......?
   finally
      Dir.Free;
   end;
end;

(* procedure TDirectory.ReCreate(List : TStringList);
var
   Index    : Integer;
   BlockNo  : ULong;
   Block    : TBlock;
   Offset   : Integer;
   Size     : Integer;
   Entry    : Pext2_dir_entry;
begin
   // sort the list?

   // there could be a fast way, but we want the easy way
   // the dir structure is in the list, so we can free all the blocks
   // we will however lose fast symbolic link info, but this should not
   // cause problems.....(I hope)
   INodeO.FreeAllBlocks;

   Index := 0; // start at the 1'st element
   while Index < List.Count do
   begin
      // Get a block.....
      BlockNo  := INodeO.AddBlock;
      Block    := TBlock.Create(Part, BlockNo);
      Entry    := nil;
      Offset   := 0;
      try
         Block.Dirty := True;
         // block.block is a block part.GetBlockSize long
         while Offset < Part.GetBlockSize do
         begin
            // is there enough room for this entry
            Size := {sizeof(ext2_dir_entry)} 8 + Length(List[Index]);
            // round Size up to a quadword boundry;
            while Size mod 4 > 0 do // cheats way
            begin
               Inc(Size);
            end;

            // is there enough room in this block?
            if Size < (Part.GetBlockSize - Offset) then
            begin
               Entry := @(PChar(Block.Block)[Offset]);
               // add the entry....
               Entry.inode    := ULong(List.Objects[Index]);
               Entry.rec_len  := size;
               Entry.name_len := Length(List[Index]);
               StrLCopy(Entry.name, PChar(List[Index]), Entry.name_len);

               Offset := Offset + Entry.rec_len;
               // special case for last entry.....

               if Index = (List.Count - 1) then
               begin
                  Entry.rec_len := Part.GetBlockSize - Offset;
                  Offset := Part.GetBlockSize;
               end;
               Index := Index + 1;
            end
            else
            begin
               // entry should!!! still point to the previous entry that fitted....
               if Assigned(Entry) then
               begin
                  Entry.rec_len := Part.GetBlockSize - (Offset + Entry.rec_len); // this should do it
                  // this block is full
                  Offset := Offset + Entry.rec_len;
               end
               else
               begin
                  raise Exception.Create('File name is to long for block!');
               end;
            end;
            if Entry.rec_len = 0 then
            begin
               raise Exception.Create('TDirectory.ReCreate error!!!!!');
            end;
         end;
      finally
         Block.Free;
      end;
   end;
end; *)

procedure TDirectory.ReCreate(List : TStringList);
var
   Index     : Integer;
   BlockNo   : ULong;
   Block     : TBlock;
   Offset    : Integer;
   Size      : Integer;
   Entry     : Pext2_dir_entry;
   Remaining : Integer;
   i         : Integer;

   procedure AllocBlock;
   begin
      if Assigned(block) then
      begin
         Block.Free;
      end;
      BlockNo := INodeO.AddBlock;
      Block   := TBlock.Create(Part, BlockNo, True);
      Block.Dirty := True;

      Offset      := 0;
      Remaining   := Part.GetBlockSize;
   end;
begin
   // sort the list?
   List.Sort;

   // We now make sure that there are no duplicate file names....
   // Because the list is sorted, and duplicates will be sequential
   for i := 0 to List.Count - 2 do
   begin
      if CompareStr(List[i], List[i + 1]) = 0 then
      begin
//         raise Exception.Create('Duplicate file name found in directory!');
         Debug('Duplicate file name found in directory!', DebugOff);
         List[i+1] := List[i+1] + '!';
      end;
   end;

   // We also do a check for invalid chars
   for i := 0 to List.Count - 1 do
   begin
      if Pos('/', List[i]) <> 0 then
      begin
         raise Exception.Create('Invalid charchters in filename ' + List[i]);
      end;
   end;


   // there could be a fast way, but we want the easy way
   // the dir structure is in the list, so we can free all the blocks
   // we will however lose fast symbolic link info, but this should not
   // cause problems.....(I hope)
   INodeO.FreeAllBlocks;

   Entry    := nil;
   Block    := nil;
   AllocBlock;
   for Index := 0 to List.Count - 1 do
   begin
      Size := 8 + Length(List[Index]);
      // round Size up to a quadword boundry;
      while Size mod 4 > 0 do // cheats way
      begin
         Inc(Size);
      end;

      if Size > Remaining then
      begin
         // finish off this entry
         Entry.rec_len := Entry.rec_len + Remaining;
         AllocBlock;
      end;

      Entry := @(PChar(Block.Block)[Offset]);

      Entry.inode    := ULong(List.Objects[Index]);
      Entry.rec_len  := Size;
      Entry.name_len := Length(List[Index]);
      StrLCopy(Entry.name, PChar(List[Index]), Entry.name_len);
      Offset      := Offset + Size;
      Remaining   := Remaining - Size;
   end;
   Entry.rec_len := Entry.rec_len + Remaining;
   Block.Free;
end;


procedure TDirectory.Rename(INode : ULong; NewName : String);
var
   Dir   : TStringList;
   i     : Integer;
begin
   Dir := TStringList.Create;
   try
      EnumDir(Dir);
      for i := 0 to Dir.Count - 1 do
      begin
         if ULong(Dir.Objects[i]) = INode then
         begin
            Dir[i] := NewName;
            break;
         end;
      end;
      ReCreate(Dir);
   finally
      Dir.Free;
   end;

end;


end.
