unit INode;

interface

uses LinuxFS, Windows, ex2, Saving, SysUtils, Blocks;

type

  TINode = class
  public
    Part          : TLinuxPartition;
    INodeNo       : ULONG;
    GroupNo       : ULONG;
    INodeInGroup  : ULONG;
    Info          : Pext2_inode;
    Table         : Pointer;
    Dirty         : Boolean;

    // these are use to speed up file writing
    GroupToUse    : ULong;
    IndCount      : ULong;
    DIndCount     : ULong;
    TIndCount     : ULong;

    IndBlockNo  : ULong;
    IndBlockO   : TBlock;
    DIndBlockNo : ULong;
    DIndBlockO  : TBlock;
    TIndBlockNo : ULong;
    TIndBlockO  : TBlock;

    constructor Create(Partition : TLinuxPartition; INode : ULong);
    destructor Destroy; override;

    procedure SaveToFile(FileName : String; Dlg : TSavingDlg; Translate : Boolean);
    procedure ReadFromFile(FileName : String; Dlg : TSavingDlg; Translate : Boolean);
    procedure ImportDirectory(Directory : String; Dlg : TSavingDlg; Translate : Boolean);

    procedure AdjustLength(Length : ULong);
    procedure Initialise(Mode : ULONG);
    function  AddBlock : ULONG;

    procedure FreeAllBlocks;

    function ModeString : String;

  end;


implementation

uses Time, WinIOCTL, ex2explore, BinFile, Forms, Utils, Dialogs;

///////////////////////////////////
// TINode
///////////////////////////////////

constructor TINode.Create(Partition : TLinuxPartition; INode : ULong);
begin
   inherited Create;

   Part := Partition;
   Dirty := False;

   INodeNo := INode;

   if (INodeNo > Part.GetSuper.s_inodes_count) then
   begin
      raise Exception.Create('INode number ' + IntToStr(INodeNo) + ' is out of range!');
   end;

   GroupNo      := Part.GetInodeGroup(INodeNo);
   INodeInGroup := (INodeNo - 1) mod Part.GetSuper.s_inodes_per_group;


   GroupToUse  := GroupNo;
   IndCount    := Part.IndirectBlockCount;
   DIndCount   := IndCount * IndCount;
   TIndCount   := DIndCount * IndCount;

   IndBlockNo  := 0;
   DIndBlockNo := 0;
   TIndBlockNo := 0;

//   LockBlockGroup(Result.GroupNo);

//   Result.Info := GetINode(Result.GroupNo, Result.INodeInGroup);
   Info := TBlockGroup(Part.LockBlockGroup(GroupNo)).GetINode(INodeInGroup);
end;

destructor TINode.Destroy;
begin
   if IndBlockNo > 0 then
   begin
      IndBlockO.Free;
   end;
   if DIndBlockNo > 0 then
   begin
      DIndBlockO.Free;
   end;
   if TIndBlockNo > 0 then
   begin
      TIndBlockO.Free;
   end;

   if Dirty then
   begin
      TBlockGroup(Part.GetBlockGroup(GroupNo)).Dirty := True;
   end;
   Part.UnLockBlockGroup(GroupNo);
   inherited Destroy;
end;

procedure TINode.SaveToFile(FileName : String; Dlg : TSavingDlg; Translate : Boolean);
var
   Size     : ULONG;
   Written  : ULONG;
   i        : Integer;
   H        : THandle;
   Buffer   : String;


   procedure ProcessBlock(BlockNo : ULONG; Indirection : Integer);
   var
      Len      : ULONG;
      Block    : TBlock;
      Actual   : DWORD;
      i        : Integer;
      iBlockNo : Integer;
      Len2Write : Integer;
   begin
      if Indirection = 0 then // this block is data
      begin
         Len := Part.GetBlockSize;
         if (Written + Len) > Size then
         begin
            Len := Size - Written;
         end;
      // lock then block
         Block := TBlock.Create(Part, BlockNo, True);
         try
            if Translate then
            begin
               Len2Write := 0;
               if Len > 0 then
               begin
                  for i := 0 to Len - 1 do
                  begin
                     if PChar(Block.Block)[i] = #10 then
                     begin
                        PChar(Buffer)[Len2Write] := #13;
                        inc(Len2Write);
                     end;
                     PChar(Buffer)[Len2Write] := PChar(Block.Block)[i];
                     inc(Len2Write);
                  end;
               end;
               // copy data into file
               WriteFile2(H, Pchar(Buffer), Len2Write, Actual, nil);
            end
            else // don't translate
            begin
               WriteFile2(H, Block.Block, Len, Actual, nil);
            end;
            Written := Written + Len;
            if Assigned(Dlg) then
            begin
               Dlg.FileStep;
            end;
         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) or Dlg.Cancel then break;
            end;
         finally
            Block.Free;
         end;
      end;
   end;
begin
   // open the file
   H := CreateFile(PChar(FileName), GENERIC_WRITE, 0, nil, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);

   Size     := info.i_size;
   Written  := 0;

   SetLength(Buffer, part.GetBlockSize * 2);

   if Assigned(Dlg) then
   begin
      Dlg.SetFile(FileName, Size div Part.GetBlockSize);
   end;
   try
      for i := 1 to EXT2_NDIR_BLOCKS do
      begin
         ProcessBlock(info.i_block[i], 0);
         if (Written >= info.i_size) or Dlg.Cancel then break;
      end;

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

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

      if (Written < info.i_size) and not Dlg.Cancel then
      begin
         ProcessBlock(info.i_block[EXT2_TIND_BLOCK], 3);
      end;
   finally
      CloseHandle(H);
   end;
end;

procedure TINode.AdjustLength(Length : ULong);
var
   BlockCount  : ULong;
   ReqCount    : ULong;
   Count       : ULong;
   i           : Integer;
begin
   BlockCount  := div2(info.i_size, Part.GetBlockSize);
   ReqCount    := div2(Length, Part.GetBlockSize);

   if BlockCount > ReqCount then
   begin
      // remove blocks...
      Count := BlockCount - ReqCount;
      for i := 1 to Count do
      begin
//         AddBlock;
         //?
      end;
   end
   else if BlockCount < ReqCount then
   begin
      // add blocks...
      Count := ReqCount - BlockCount;
      for i := 1 to Count do
      begin
//         AddBlock(???);
      end;
      // or AddBlocks(Count ?);
   end;
   // Block count should now be correct
   info.i_size := Length;
   Dirty := True;
end;

procedure TINode.Initialise(Mode : ULONG);
var
   i : Integer;
begin
   info.i_mode := USHORT(Mode);
   info.i_mode := info.i_mode or CreateFlags;
   info.i_uid  := 0;
   info.i_size := 0;
   info.i_atime  := CurrentCTime; // some time!!!!!
   info.i_ctime  := CurrentCTime;
   info.i_mtime  := CurrentCTime;
   info.i_dtime  := 0;
   info.i_gid    := 0;
   info.i_links_count := 0;
   info.i_blocks      := 0;
   info.i_flags       := 0;

   info.i_version     := 0;
   info.i_file_acl    := 0;
   info.i_dir_acl     := 0;
   info.i_faddr       := 0;

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

   info.l_i_frag      := 0;
   info.l_i_fsize     := 0;

   Dirty := True;
end;

function TINode.AddBlock : ULONG;
var
   NewBlock  : ULONG;
   NextBlock : ULong;
   Indirect  : TBlock;
   DIndirect : TBlock;
   TIndirect : TBlock;
   IndNo     : ULong;
   DIndNo    : ULong;
   BlockNo   : ULong;
begin
   // getting the block is easy, where to store the pointer is the trick.....
   // we have to look at the size of the file, the block count does not mean anything(much)
//   NewBlock := 0;


   NextBlock := div2(info.i_size, part.GetBlockSize) + 1;
   Debug('New Block needs to to go in ' + IntToStr(NextBlock), DebugLow);
   if NextBlock <= EXT2_NDIR_BLOCKS then
   begin
      NewBlock := Part.AllocDataBlock(GroupToUse); // in the group the inode is in
      Debug('New Block ' + IntToStr(NewBlock), DebugHigh);
      info.i_block[NextBlock] := NewBlock;
      info.i_blocks := info.i_blocks + (Part.GetBlockSize div 512);
      info.i_size := info.i_size + Part.GetBlockSize;
   end
   else
   begin
      NextBlock := NextBlock - EXT2_NDIR_BLOCKS;
      if NextBlock <= IndCount then
      begin
         // is the indirect block allocated?
         if info.i_block[EXT2_IND_BLOCK] = 0 then
         begin
            info.i_block[EXT2_IND_BLOCK] := Part.AllocDataBlock(GroupToUse);
            Indirect := TBlock.Create(part, info.i_block[EXT2_IND_BLOCK], False);
            Indirect.ZeroBlock;
            Indirect.Free;

            // do I have to increment the block count? Yes
            info.i_blocks  := info.i_blocks + (Part.GetBlockSize div 512);
            Dirty := True;
         end;

         if (IndBlockNo = info.i_block[EXT2_IND_BLOCK]) and (IndBlockNo <> 0) then
         begin
            Indirect   := IndBlockO; // use the existing block
         end
         else
         begin
            if IndBlockNo > 0 then
            begin
               IndBlockO.Free;
            end;
            IndBlockNo := info.i_block[EXT2_IND_BLOCK];
            IndBlockO  := TBlock.Create(part, IndBlockNo, True); // get the block and store it
            Indirect := IndBlockO;
         end;
         // there is a block by now.
         try
            NewBlock := Part.AllocDataBlock(GroupToUse);
            Indirect.SetIndirectBlockNo(NextBlock, NewBlock);
            Indirect.Dirty := True;
            info.i_blocks  := info.i_blocks + (Part.GetBlockSize div 512);
            info.i_size    := info.i_size + Part.GetBlockSize;
         finally
//            Indirect.Free;
         end;
      end
      else
      begin
         // we may have a 2* indirect
         // I am sure that there is a better (recursive) way of doing this, but I have
         // no idea what it is.  If anybody cares to share it with me...
         NextBlock := NextBlock - IndCount;
         if NextBlock <= DIndCount then
         begin
            // it is a 2 * indirect
            // if it is the 1'st block, we need to alocate the 2nd indir block
            if info.i_block[EXT2_DIND_BLOCK] = 0 then
            begin
               info.i_block[EXT2_DIND_BLOCK] := Part.AllocDataBlock(GroupToUse);
               info.i_blocks := info.i_blocks + (Part.GetBlockSize div 512);
               DIndirect := TBlock.Create(Part, info.i_block[EXT2_DIND_BLOCK], False);
               DIndirect.ZeroBlock;
               DIndirect.Free;
            end;

            if (DIndBlockNo = info.i_block[EXT2_DIND_BLOCK]) and (DIndBlockNo <> 0) then
            begin
               DIndirect := DIndBlockO;
            end
            else
            begin
               if DIndBlockNo > 0 then
               begin
                  DIndBlockO.Free;
               end;
               DIndBlockNo := info.i_block[EXT2_DIND_BLOCK];
               DIndBlockO  := TBlock.Create(Part, DIndBlockNo, True);
               DIndirect   := DIndBlockO;
            end;
            try
               // which DIndirect block points to the indirect?
               IndNo := div2(NextBlock, IndCount);
               // make sure that this indirect block exists
               if DIndirect.GetIndirectBlockNo(IndNo) = 0 then
               begin
                  // we will have to allocate it...
                  DIndirect.SetIndirectBlockNo(IndNo, Part.AllocDataBlock(GroupToUse));
                  info.i_blocks  := info.i_blocks + (Part.GetBlockSize div 512);
                  Indirect := TBlock.Create(Part, DIndirect.GetIndirectBlockNo(IndNo), False);
                  Indirect.ZeroBlock;
                  Indirect.Free;
               end;

               if (IndBlockNo = DIndirect.GetIndirectBlockNo(IndNo)) and (IndBlockNo <> 0) then
               begin
                  Indirect   := IndBlockO; // use the existing block
               end
               else
               begin
                  if IndBlockNo > 0 then
                  begin
                     IndBlockO.Free;
                  end;
                  IndBlockNo := DIndirect.GetIndirectBlockNo(IndNo);
                  IndBlockO  := TBlock.Create(part, IndBlockNo, True); // get the block and store it
                  Indirect := IndBlockO;
               end;
////               Indirect := TBlock.Create(Part, DIndirect.GetIndirectBlockNo(IndNo));
               // there is a block by now.

               // now which block in this indirect one?????
               BlockNo := NextBlock - ((IndNo - 1) * IndCount);

               // now allocate the block
               NewBlock := Part.AllocDataBlock(GroupToUse);
               Indirect.SetIndirectBlockNo(BlockNo, NewBlock);
               info.i_blocks  := info.i_blocks + (Part.GetBlockSize div 512);
               info.i_size := info.i_size + Part.GetBlockSize;

//               Indirect.Free;
            finally
//               DIndirect.Free;
            end;
         end
         else
         begin
            // I will assume that a file will always fit into the tripple indirect block.
            // if not then something will eventualy go wrong (at about 16 Gig for a 1k fs)
            NextBlock := NextBlock - DIndCount;
            if NextBlock <= TIndCount then // now I do check....
{start Tind}begin
               if info.i_block[EXT2_TIND_BLOCK] = 0 then
               begin
                  info.i_block[EXT2_TIND_BLOCK] := Part.AllocDataBlock(GroupToUse);
                  info.i_blocks := info.i_blocks + (Part.GetBlockSize div 512);
                  TIndirect := TBlock.Create(Part, info.i_block[EXT2_TIND_BLOCK], True);
                  TIndirect.ZeroBlock;
                  TIndirect.Free;
               end;

               if (TIndBlockNo = info.i_block[EXT2_TIND_BLOCK]) and (TIndBlockNo <> 0) then
               begin
                  TIndirect   := TIndBlockO; // use the existing block
               end
               else
               begin
                  if TIndBlockNo > 0 then
                  begin
                     TIndBlockO.Free;
                  end;
                  TIndBlockNo := info.i_block[EXT2_TIND_BLOCK];
                  TIndBlockO  := TBlock.Create(part, TIndBlockNo, True); // get the block and store it
                  TIndirect   := TIndBlockO;
               end;

//               TIndirect := TBlock.Create(Part, info.i_block[EXT2_TIND_BLOCK]);
               try
                  // now see which DIndirect to use
                  DIndNo := div2(NextBlock, DIndCount);
                  // make sure that this indirect block exists
                  if TIndirect.GetIndirectBlockNo(DIndNo) = 0 then
                  begin
                     // we will have to allocate it...
                     TIndirect.SetIndirectBlockNo(DIndNo, Part.AllocDataBlock(GroupToUse));
                     info.i_blocks  := info.i_blocks + (Part.GetBlockSize div 512);
                     DIndirect := TBlock.Create(Part, TIndirect.GetIndirectBlockNo(DIndNo), True);
                     DIndirect.ZeroBlock;
                     DIndirect.Free;
                  end;

                  // get the dindirect block
                  if (DIndBlockNo = TIndirect.GetIndirectBlockNo(DIndNo)) and (DIndBlockNo <> 0) then
                  begin
                     DIndirect := DIndBlockO;
                  end
                  else
                  begin
                     if DIndBlockNo > 0 then
                     begin
                        DIndBlockO.Free;
                     end;
                     DIndBlockNo := TIndirect.GetIndirectBlockNo(DIndNo);
                     DIndBlockO  := TBlock.Create(Part, DIndBlockNo, True);
                     DIndirect   := DIndBlockO;
                  end;
// //                  DIndirect := TBlock.Create(Part, TIndirect.GetIndirectBlockNo(DIndNo));
                  try
                     // look up the indirect block
                     NextBlock := NextBlock - ((DIndNo - 1) * DIndCount);

                     IndNo := div2(NextBlock, IndCount);
                     BlockNo := NextBlock - ((IndNo - 1) * IndCount);

                     // see if the indirect block exists
                     if DIndirect.GetIndirectBlockNo(IndNo) = 0 then // we have to create it
                     begin
                        DIndirect.SetIndirectBlockNo(IndNo, Part.AllocDataBlock(GroupToUse));
                        info.i_blocks := info.i_blocks + (Part.GetBlockSize div 512);
                        Indirect := TBlock.Create(Part, DIndirect.GetIndirectBlockNo(IndNo), True);
                        Indirect.ZeroBlock;
                        Indirect.Free;
                     end;

                     if (IndBlockNo = DIndirect.GetIndirectBlockNo(IndNo)) and (IndBlockNo <> 0) then
                     begin
                        Indirect   := IndBlockO; // use the existing block
                     end
                     else
                     begin
                        if IndBlockNo > 0 then
                        begin
                           IndBlockO.Free;
                        end;
                        IndBlockNo := DIndirect.GetIndirectBlockNo(IndNo);
                        IndBlockO  := TBlock.Create(part, IndBlockNo, True); // get the block and store it
                        Indirect := IndBlockO;
                     end;
// //                     Indirect := TBlock.Create(Part, DIndirect.GetIndirectBlockNo(IndNo));

                     // now we have the indirect block, add a new data block.....
                     NewBlock := Part.AllocDataBlock(GroupToUse);
                     Indirect.SetIndirectBlockNo(BlockNo, NewBlock);
                     info.i_blocks  := info.i_blocks + (Part.GetBlockSize div 512);
                     info.i_size := info.i_size + Part.GetBlockSize;

//                     Indirect.Free;
                  finally
//                     DIndirect.Free;
                  end;
               finally
//                  TIndirect.Free;
               end;
            end
            else
            begin
               raise Exception.Create('File is too big for this block size!');
            end;
         end;
      end;
   end;
   info.i_mtime := CurrentCTime;
   Result := NewBlock;
end;

procedure TINode.FreeAllBlocks;
begin
   Part.FreeINodeBlocks(info);
   info.i_size    := 0;
   info.i_blocks  := 0;
   info.i_mtime   := CurrentCTime;
end;

procedure TINode.ReadFromFile(FileName : String; Dlg : TSavingDlg; Translate : Boolean);
var
   Block    : ULong;
   BlockO   : TBlock;
   F        : TBinaryFile;
   Len      : Integer;
   Blocks   : ULong;
begin
   F := TBinaryFile.Create;
   F.Assign(FileName);
   try
      try
         F.Open(0); // for read
         Len := F.FileSize;
         Blocks := div2(F.FileSize, Part.GetBlockSize);
         if Part.GetSuper.s_free_blocks_count < Blocks then
         begin
            Debug('Only ' + IntToStr(Part.GetSuper.s_free_blocks_count) + ' block available (required = ' + IntToStr(Blocks) + ')', DebugOff);
            MessageDlg('Not enough free space for ''' + FileName + '''', mtError, [mbOK], 0);
         end
         else
         begin
            if Assigned(Dlg) then
            begin
               Dlg.SetFile(FileName, Blocks);
            end;
            FreeAllBlocks;
            // we have to set the time and such...
            while Len > 0 do
            begin
               Block := AddBlock;
               Debug('New Block ' + IntToStr(Block), DebugHigh);

               // lock the block
               BlockO := TBlock.Create(Part, Block, False);
               try
                  // read a block of the file and copy it...
                  if Len > Part.GetBlockSize then
                  begin
                     F.BlockRead2(BlockO.Block, Part.GetBlockSize);
                     Len := Len - Part.GetBlockSize;
                  end
                  else
                  begin
                     F.BlockRead2(BlockO.Block, Len);
                     Len := 0;
                  end;
                  BlockO.Dirty := True;
               finally
                  BlockO.Free;
                  if Assigned(Dlg) then
                  begin
                     Dlg.FileStep;
                  end;
               end;
            end;
            // the INode will now be the max len that the allocated blocks can hold
            // we should now set it to the correct length
            Dirty := True;
            info.i_size := F.FileSize;
         end;
      finally
         F.Free;
      end;
   except
      on E : EInOutError do
      begin
         Debug('IO Error ' + IntToStr(E.ErrorCode) + ' in ImportData ''' + FileName + '''', DebugOff);
      end;
   end;
end;

procedure TINode.ImportDirectory(Directory : String; Dlg : TSavingDlg; Translate : Boolean);
var
   FileList : TFileList;
   i        : Integer;
   NewINode : ULong;
   Name     : String;
   INodeO   : TINode;
begin
   if Assigned(Dlg) then
   begin
      Dlg.SetFile(Directory, 1);
   end;
   FileList := TFileList.Create;
   FileList.SetAttrMask(faAnyFile);
   FileList.Calculate(Directory + '\*.*');
   if Assigned(Dlg) then
   begin
      Dlg.AddFiles(FileList.Count - 2);
   end;
   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 := Part.MakeDirectory(INodeNo, FileList.Get(i).Name);
            INodeO := TINode.Create(Part, NewINode);
            try
               INodeO.ImportDirectory(Directory + '\' + Name, Dlg, Translate);
            finally
               INodeO.Free;
            end;
         end;
      end
      else if FileList.Get(i).IsFile then
      begin
         Debug('File : ' + FileList.Get(i).Name, DebugMedium);
         NewINode := Part.CreateFile(INodeNo, FileList.Get(i).Name);
         INodeO := TINode.Create(Part, NewINode);
         try
            INodeO.ReadFromFile(Directory + '\' + FileList.Get(i).Name, Dlg, Translate);
         finally
            INodeO.Free;
         end;
      end;
   end;
   FileList.Free;
end;

function TINode.ModeString : String;
begin
   // we will create a string which looks like
   // srwxrwxrwx
   // depending of course on the mode of the file.....

   // what ever this bit is called
   if (info.i_mode and S_IFMT) = S_IFSOCK then
   begin
      Result := 's';
   end
   else if (info.i_mode and S_IFMT) = S_IFLNK then
   begin
      Result := 'l';
   end
   else if (info.i_mode and S_IFMT) = S_IFBLK then
   begin
      Result := 'b';
   end
   else if (info.i_mode and S_IFMT) = S_IFDIR then
   begin
      Result := 'd';
   end
   else if (info.i_mode and S_IFMT) = S_IFCHR then
   begin
      Result := 'c';
   end
   else if (info.i_mode and S_IFMT) = S_IFIFO then
   begin
      Result := 'p'; // I think (p for pipe?)
   end
   else
   begin
      Result := '-';
   end;

   // the user attributes
   if (info.i_mode and S_IRUSR) = S_IRUSR then
   begin
      Result := Result + 'r';
   end
   else
   begin
      Result := Result + '-';
   end;

   if (info.i_mode and S_IWUSR) = S_IWUSR then
   begin
      Result := Result + 'w';
   end
   else
   begin
      Result := Result + '-';
   end;

   if (info.i_mode and S_IXUSR) = S_IXUSR then
   begin
      if (info.i_mode and S_ISUID) = S_ISUID then
      begin
         Result := Result + 's';
      end
      else
      begin
         Result := Result + 'x';
      end;
   end
   else
   begin
      Result := Result + '-';
   end;

   // group attributes
   if (info.i_mode and S_IRGRP) = S_IRGRP then
   begin
      Result := Result + 'r';
   end
   else
   begin
      Result := Result + '-';
   end;

   if (info.i_mode and S_IWGRP) = S_IWGRP then
   begin
      Result := Result + 'w';
   end
   else
   begin
      Result := Result + '-';
   end;

   if (info.i_mode and S_IXGRP) = S_IXGRP then
   begin
      if (info.i_mode and S_ISGID) = S_ISGID then
      begin
         Result := Result + 's';
      end
      else
      begin
         Result := Result + 'x';
      end
   end
   else
   begin
      Result := Result + '-';
   end;

   // other attributes
   if (info.i_mode and S_IROTH) = S_IROTH then
   begin
      Result := Result + 'r';
   end
   else
   begin
      Result := Result + '-';
   end;

   if (info.i_mode and S_IWOTH) = S_IWOTH then
   begin
      Result := Result + 'w';
   end
   else
   begin
      Result := Result + '-';
   end;

   if (info.i_mode and S_IXOTH) = S_IXOTH then
   begin
      if (info.i_mode and S_IFMT) = S_ISVTX then
      begin
         Result := Result + 't';
      end
      else
      begin
         Result := Result + 'x';
      end;
   end
   else
   begin
      Result := Result + '-';
   end;
end;

end.
